├── .gitignore ├── .npmignore ├── tsconfig.json ├── jest.config.js ├── src ├── config.ts ├── types.ts ├── categories.ts ├── reportSummary.ts ├── __tests__ │ └── reportSummary.test.ts ├── audienseClient.ts ├── index.ts ├── promts.ts └── baselines.ts ├── smithery.yaml ├── Dockerfile ├── package.json ├── README.md └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .gitignore 3 | .github/ 4 | .vscode/ 5 | node_modules/ 6 | src/ 7 | jest.config.js 8 | tsconfig.json 9 | smithery.yaml 10 | Dockerfile 11 | *.test.ts 12 | *.test.js 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | extensionsToTreatAsEsm: ['.ts'], 5 | moduleNameMapper: { 6 | '^(\\.{1,2}/.*)\\.js$': '$1', 7 | }, 8 | transform: { 9 | '^.+\\.tsx?$': [ 10 | 'ts-jest', 11 | { 12 | useESM: true, 13 | }, 14 | ], 15 | }, 16 | // Exclude the build directory from test paths 17 | testPathIgnorePatterns: ['/node_modules/', '/build/'], 18 | }; 19 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const AUDIENSE_API_BASE = "https://api.audiense.com/v1"; 2 | 3 | export const CLIENT_ID = process.env.AUDIENSE_CLIENT_ID || ""; 4 | export const CLIENT_SECRET = process.env.AUDIENSE_CLIENT_SECRET || ""; 5 | 6 | if (!CLIENT_ID || !CLIENT_SECRET) { 7 | console.error("Missing Audiense API credentials. Check your environment variables."); 8 | } 9 | 10 | export const TWITTER_BEARER_TOKEN = process.env.TWITTER_BEARER_TOKEN || ""; 11 | export const TWITTER_API_BASE = "https://api.twitter.com/2"; -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - audienseClientId 10 | - audienseClientSecret 11 | properties: 12 | audienseClientId: 13 | type: string 14 | description: Client ID for the Audiense API. 15 | audienseClientSecret: 16 | type: string 17 | description: Client Secret for the Audiense API. 18 | twitterBearerToken: 19 | type: string 20 | description: Bearer Token for the Twitter API (optional). 21 | commandFunction: 22 | # A function that produces the CLI command to start the MCP on stdio. 23 | |- 24 | (config) => ({ command: 'node', args: ['build/index.js'], env: { AUDIENSE_CLIENT_ID: config.audienseClientId, AUDIENSE_CLIENT_SECRET: config.audienseClientSecret, TWITTER_BEARER_TOKEN: config.twitterBearerToken || '' } }) 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | # Use an official Node.js runtime as the base image 3 | FROM node:18-alpine AS builder 4 | 5 | # Set the working directory 6 | WORKDIR /app 7 | 8 | # Copy package.json and package-lock.json for installing dependencies 9 | COPY package.json package-lock.json ./ 10 | 11 | # Install dependencies (ignoring scripts to avoid running them yet) 12 | RUN npm install --ignore-scripts 13 | 14 | # Copy the rest of the application code 15 | COPY src ./src 16 | COPY tsconfig.json ./ 17 | 18 | # Build the TypeScript code 19 | RUN npm run build 20 | 21 | # Use a lighter weight image for running the application 22 | FROM node:18-alpine 23 | 24 | # Set the working directory 25 | WORKDIR /app 26 | 27 | # Copy the built code and node_modules from the builder stage 28 | COPY --from=builder /app/build ./build 29 | COPY --from=builder /app/node_modules ./node_modules 30 | COPY --from=builder /app/package.json ./package.json 31 | 32 | # Environment variables (should be replaced with actual credentials in production) 33 | ENV AUDIENSE_CLIENT_ID=your_client_id_here 34 | ENV AUDIENSE_CLIENT_SECRET=your_client_secret_here 35 | ENV TWITTER_BEARER_TOKEN=your_token_here 36 | 37 | # Run the application 38 | CMD ["node", "build/index.js"] 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-audiense-insights", 3 | "version": "0.2.0", 4 | "type": "module", 5 | "bin": { 6 | "audiense-insights": "./build/index.js" 7 | }, 8 | "scripts": { 9 | "build": "tsc && chmod +x build/index.js", 10 | "test": "jest", 11 | "prepublishOnly": "npm run build" 12 | }, 13 | "files": [ 14 | "build", 15 | "README.md", 16 | "LICENSE.md" 17 | ], 18 | "keywords": [ 19 | "audiense", 20 | "mcp", 21 | "claude", 22 | "ai", 23 | "audience-analysis", 24 | "marketing-insights", 25 | "social-media-analytics" 26 | ], 27 | "author": "Audiense", 28 | "license": "Apache-2.0", 29 | "description": "MCP server for Claude that provides marketing insights and audience analysis from Audiense reports, covering demographic, cultural, influencer, and content engagement analysis", 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/AudienseCo/mcp-audiense-insights.git" 33 | }, 34 | "homepage": "https://github.com/AudienseCo/mcp-audiense-insights#readme", 35 | "bugs": { 36 | "url": "https://github.com/AudienseCo/mcp-audiense-insights/issues" 37 | }, 38 | "dependencies": { 39 | "@modelcontextprotocol/sdk": "^1.4.1", 40 | "base-64": "^1.0.0", 41 | "dotenv": "^16.4.7", 42 | "node-fetch": "^3.3.2", 43 | "zod": "^3.24.1" 44 | }, 45 | "devDependencies": { 46 | "@types/base-64": "^1.0.2", 47 | "@types/jest": "^29.5.14", 48 | "@types/node": "^22.12.0", 49 | "jest": "^29.7.0", 50 | "ts-jest": "^29.2.6", 51 | "typescript": "^5.7.3" 52 | }, 53 | "engines": { 54 | "node": ">=18" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type AuthResponse = { 2 | access_token: string; 3 | expires_in: number; 4 | }; 5 | 6 | export type IntelligenceReport = { 7 | id: string; 8 | title: string; 9 | segmentation_type: string; 10 | created_at: string; 11 | status: string; 12 | }; 13 | 14 | export type IntelligenceReportsResponse = { 15 | reports: IntelligenceReport[]; 16 | cursor: { 17 | cursor: { 18 | next: number; 19 | prev: number; 20 | }; 21 | }; 22 | }; 23 | 24 | export type ReportInfoResponse = { 25 | title: string; 26 | status: string; 27 | segmentation_type: string; 28 | full_audience?: { 29 | size?: number; 30 | audience_influencers_id?: string; 31 | }; 32 | segments?: { 33 | id: string; 34 | title: string; 35 | size: number; 36 | audience_influencers_id?: string; 37 | }[]; 38 | audience_influencers_id?: string; 39 | public: boolean; 40 | links?: { app?: string; public?: string }; 41 | errors?: string[]; 42 | }; 43 | 44 | export type InsightValue = { 45 | key: string; 46 | value: number; 47 | }; 48 | 49 | export type SegmentSummary = { 50 | id: string; 51 | title: string; 52 | size: number; 53 | insights: Record; 54 | influencers: any[] | null; 55 | }; 56 | 57 | export type ReportSummaryResponse = { 58 | id?: string; 59 | title: string; 60 | status?: string; 61 | segmentation_type?: string; 62 | full_audience_size?: number; 63 | segments?: SegmentSummary[]; 64 | links?: { app?: string; public?: string }; 65 | message?: string; 66 | }; 67 | -------------------------------------------------------------------------------- /src/categories.ts: -------------------------------------------------------------------------------- 1 | export const CATEGORIES = [ 2 | { "label": "Airlines", "value": "Airlines" }, 3 | { "label": "Alcohol", "value": "Alcohol" }, 4 | { "label": "Apps", "value": "Apps" }, 5 | { "label": "Beauty", "value": "Beauty" }, 6 | { "label": "Beverages", "value": "Beverages" }, 7 | { "label": "Black community", "value": "Black community" }, 8 | { "label": "Books", "value": "Books" }, 9 | { "label": "Business", "value": "Business" }, 10 | { "label": "Celebrities", "value": "Celebrities" }, 11 | { "label": "Climate Change", "value": "Climate Change" }, 12 | { "label": "Computer Game", "value": "Computer Game" }, 13 | { "label": "Conference", "value": "Conference" }, 14 | { "label": "Corporate Social Responsibility", "value": "Corporate Social Responsibility" }, 15 | { "label": "Culture", "value": "Culture" }, 16 | { "label": "Ecommerce", "value": "Ecommerce" }, 17 | { "label": "Education", "value": "Education" }, 18 | { "label": "Electronics", "value": "Electronics" }, 19 | { "label": "Entertainment", "value": "Entertainment" }, 20 | { "label": "Erotic", "value": "Erotic" }, 21 | { "label": "Events", "value": "Events" }, 22 | { "label": "Family", "value": "Family" }, 23 | { "label": "Fashion", "value": "Fashion" }, 24 | { "label": "Finance", "value": "Finance" }, 25 | { "label": "Fitness", "value": "Fitness" }, 26 | { "label": "Food&drink", "value": "Food&drink" }, 27 | { "label": "Fun", "value": "Fun" }, 28 | { "label": "Gambling", "value": "Gambling" }, 29 | { "label": "Gov officials & agencies", "value": "Gov officials & agencies" }, 30 | { "label": "Healthcare", "value": "Healthcare" }, 31 | { "label": "Hobbies and interest", "value": "Hobbies and interest" }, 32 | { "label": "Household", "value": "Household" }, 33 | { "label": "Internet", "value": "Internet" }, 34 | { "label": "LGBTQ+", "value": "LGBTQ+" }, 35 | { "label": "Magazines", "value": "Magazines" }, 36 | { "label": "Media", "value": "Media" }, 37 | { "label": "Motor", "value": "Motor" }, 38 | { "label": "Movies", "value": "Movies" }, 39 | { "label": "Music", "value": "Music" }, 40 | { "label": "News", "value": "News" }, 41 | { "label": "Newspapers", "value": "Newspapers" }, 42 | { "label": "Night Life", "value": "Night Life" }, 43 | { "label": "Non profits", "value": "Non profits" }, 44 | { "label": "Online Show", "value": "Online Show" }, 45 | { "label": "Pets", "value": "Pets" }, 46 | { "label": "Place", "value": "Place" }, 47 | { "label": "Politics", "value": "Politics" }, 48 | { "label": "Professional Association", "value": "Professional Association" }, 49 | { "label": "Radio", "value": "Radio" }, 50 | { "label": "Religion", "value": "Religion" }, 51 | { "label": "Restaurants&cafes", "value": "Restaurants&cafes" }, 52 | { "label": "Science", "value": "Science" }, 53 | { "label": "Services", "value": "Services" }, 54 | { "label": "Shopping", "value": "Shopping" }, 55 | { "label": "Society", "value": "Society" }, 56 | { "label": "Sports", "value": "Sports" }, 57 | { "label": "Technology", "value": "Technology" }, 58 | { "label": "Telecom", "value": "Telecom" }, 59 | { "label": "Top brands", "value": "Top brands" }, 60 | { "label": "Travel", "value": "Travel" }, 61 | { "label": "TV", "value": "TV" }, 62 | { "label": "Blogs", "value": "Blogs" }, 63 | { "label": "Facebook", "value": "Facebook" }, 64 | { "label": "Instagram", "value": "Instagram" }, 65 | { "label": "Journalist", "value": "Journalist" }, 66 | { "label": "LinkedIn", "value": "LinkedIn" }, 67 | { "label": "Medium", "value": "Medium" }, 68 | { "label": "Pinterest", "value": "Pinterest" }, 69 | { "label": "Podcasts", "value": "Podcasts" }, 70 | { "label": "Snapchat", "value": "Snapchat" }, 71 | { "label": "TikTok", "value": "TikTok" }, 72 | { "label": "Twitch", "value": "Twitch" }, 73 | { "label": "YouTube", "value": "YouTube" } 74 | ]; -------------------------------------------------------------------------------- /src/reportSummary.ts: -------------------------------------------------------------------------------- 1 | import { getReportInfo, getAudienceInsights, compareAudienceInfluencers } from "./audienseClient.js"; 2 | import { InsightValue, ReportSummaryResponse } from "./types.js"; 3 | 4 | /** 5 | * Fetches the top N values for each insight type 6 | * @param audienceId The audience ID to fetch insights for 7 | * @param insightTypes Array of insight types to fetch 8 | * @param topCount Number of top values to return for each insight 9 | * @returns Record of insight types to their top values or null 10 | */ 11 | async function getTopInsights( 12 | audienceId: string, 13 | insightTypes: string[], 14 | topCount: number = 5 15 | ): Promise> { 16 | try { 17 | const insightsData = await getAudienceInsights(audienceId, insightTypes); 18 | 19 | if (!insightsData || !insightsData.insights.length) { 20 | console.error(`No insights found for audience ${audienceId} and insight types ${insightTypes.join(',')}`); 21 | return Object.fromEntries(insightTypes.map(type => [type, null])); 22 | } 23 | 24 | // Process each insight type 25 | const result: Record = {}; 26 | 27 | for (const insight of insightsData.insights) { 28 | // Sort values by percentage (descending) and take top N 29 | result[insight.name] = insight.values 30 | .sort((a, b) => parseFloat(b.value) - parseFloat(a.value)) 31 | .slice(0, topCount) 32 | .map(v => ({ key: v.key, value: parseFloat(v.value) })); 33 | } 34 | 35 | return result; 36 | } catch (error) { 37 | console.error(`Error fetching insights for audience ${audienceId}:`, error); 38 | return Object.fromEntries(insightTypes.map(type => [type, null])); 39 | } 40 | } 41 | 42 | /** 43 | * Fetches top influencers for a segment compared to the full audience 44 | * @param segmentId The segment ID to fetch influencers for 45 | * @param fullAudienceId The full audience ID for comparison 46 | * @param count Number of influencers to return 47 | * @returns Array of influencers or null if error 48 | */ 49 | async function getTopInfluencers( 50 | segmentId: string, 51 | baselineAudienceId: string, 52 | count: number = 10 53 | ): Promise { 54 | try { 55 | const influencersData = await compareAudienceInfluencers( 56 | segmentId, 57 | baselineAudienceId, 58 | undefined, // cursor 59 | count // count 60 | ); 61 | 62 | if (!influencersData || !influencersData.influencers.length) { 63 | console.error(`No influencers found for segment ${segmentId}`); 64 | return null; 65 | } 66 | 67 | // Filter influencers to include only the specified fields 68 | return influencersData.influencers.map(influencer => ({ 69 | id: influencer.id, 70 | affinity: influencer.affinity, 71 | baseline_affinity: influencer.baseline_affinity, 72 | uniqueness: influencer.uniqueness, 73 | followers_count: influencer.twitter?.public_metrics?.followers_count, 74 | url: influencer.twitter?.url, 75 | name: influencer.twitter?.name, 76 | verified_type: influencer.twitter?.verified_type, 77 | description: influencer.twitter?.description, 78 | profile_image_url: influencer.twitter?.profile_image_url, 79 | verified: influencer.twitter?.verified, 80 | username: influencer.twitter?.username 81 | })); 82 | } catch (error) { 83 | console.error(`Error fetching influencers for segment ${segmentId}:`, error); 84 | return null; 85 | } 86 | } 87 | 88 | /** 89 | * Generates a comprehensive summary of an Audiense report 90 | * @param reportId The ID of the report to summarize 91 | * @returns A structured summary of the report or null if error 92 | */ 93 | export async function generateReportSummary(reportId: string): Promise { 94 | const insightTypes = ['bio_keyword', 'country', 'age', 'city', 'language', 'gender', 'interest']; 95 | 96 | try { 97 | // Fetch the report info 98 | const reportInfo = await getReportInfo(reportId); 99 | 100 | if (!reportInfo) { 101 | console.error(`Failed to retrieve report info for ID: ${reportId}`); 102 | return null; 103 | } 104 | 105 | if (reportInfo.status === "pending") { 106 | return { 107 | title: reportInfo.title, 108 | status: reportInfo.status, 109 | message: "Report is still processing. Try again later." 110 | }; 111 | } 112 | 113 | if (!reportInfo.segments || reportInfo.segments.length === 0) { 114 | return { 115 | title: reportInfo.title, 116 | status: reportInfo.status, 117 | message: "Report has no segments to analyze." 118 | }; 119 | } 120 | 121 | // Get the full audience ID for influencer comparison 122 | const fullAudienceId = reportInfo.full_audience?.audience_influencers_id; 123 | 124 | // Process all segments in parallel 125 | const segmentPromises = reportInfo.segments.map(async segment => { 126 | const segmentInfluencersId = segment.audience_influencers_id; 127 | 128 | // Only attempt to get influencers if we have valid IDs 129 | const influencers = fullAudienceId && segmentInfluencersId ? 130 | await getTopInfluencers(segmentInfluencersId, fullAudienceId) : 131 | null; 132 | const insights = await getTopInsights(segment.id, insightTypes); 133 | 134 | return { 135 | id: segment.id, 136 | title: segment.title, 137 | size: segment.size, 138 | insights, 139 | influencers 140 | }; 141 | }); 142 | 143 | const segments = await Promise.all(segmentPromises); 144 | 145 | // Return the complete report summary 146 | return { 147 | id: reportId, 148 | title: reportInfo.title, 149 | segmentation_type: reportInfo.segmentation_type, 150 | full_audience_size: reportInfo.full_audience?.size, 151 | segments, 152 | links: reportInfo.links 153 | }; 154 | } catch (error) { 155 | console.error(`Error generating report summary for ${reportId}:`, error); 156 | return null; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/__tests__/reportSummary.test.ts: -------------------------------------------------------------------------------- 1 | import { generateReportSummary } from '../reportSummary.js'; 2 | import { getReportInfo, getAudienceInsights, compareAudienceInfluencers } from '../audienseClient.js'; 3 | import { ReportInfoResponse } from '../types.js'; 4 | 5 | // Mock the audienseClient functions 6 | jest.mock('../audienseClient.js', () => ({ 7 | getReportInfo: jest.fn(), 8 | getAudienceInsights: jest.fn(), 9 | compareAudienceInfluencers: jest.fn() 10 | })); 11 | 12 | describe('Report Summary', () => { 13 | beforeEach(() => { 14 | jest.clearAllMocks(); 15 | }); 16 | 17 | it('should handle pending reports', async () => { 18 | // Mock a pending report 19 | (getReportInfo as jest.Mock).mockResolvedValue({ 20 | title: 'Test Report', 21 | status: 'pending', 22 | segmentation_type: 'test' 23 | } as ReportInfoResponse); 24 | 25 | const result = await generateReportSummary('test-report-id'); 26 | 27 | expect(result).toEqual({ 28 | title: 'Test Report', 29 | status: 'pending', 30 | message: 'Report is still processing. Try again later.' 31 | }); 32 | expect(getReportInfo).toHaveBeenCalledWith('test-report-id'); 33 | }); 34 | 35 | it('should handle reports with no segments', async () => { 36 | // Mock a report with no segments 37 | (getReportInfo as jest.Mock).mockResolvedValue({ 38 | title: 'Test Report', 39 | status: 'completed', 40 | segmentation_type: 'test', 41 | segments: [], 42 | public: false 43 | } as ReportInfoResponse); 44 | 45 | const result = await generateReportSummary('test-report-id'); 46 | 47 | expect(result).toEqual({ 48 | title: 'Test Report', 49 | status: 'completed', 50 | message: 'Report has no segments to analyze.' 51 | }); 52 | }); 53 | 54 | it('should generate a complete report summary', async () => { 55 | // Mock report info 56 | (getReportInfo as jest.Mock).mockResolvedValue({ 57 | title: 'Test Report', 58 | status: 'completed', 59 | segmentation_type: 'test', 60 | full_audience: { 61 | size: 1000, 62 | audience_influencers_id: 'full-audience-id' 63 | }, 64 | public: false, 65 | segments: [ 66 | { 67 | id: 'segment-1', 68 | title: 'Segment 1', 69 | size: 500, 70 | audience_influencers_id: 'segment-1-influencers' 71 | }, 72 | { 73 | id: 'segment-2', 74 | title: 'Segment 2', 75 | size: 500, 76 | audience_influencers_id: 'segment-2-influencers' 77 | } 78 | ], 79 | links: { 80 | app: 'https://app.example.com/report', 81 | public: 'https://public.example.com/report' 82 | } 83 | } as ReportInfoResponse); 84 | 85 | // Mock insights for segment 1 86 | (getAudienceInsights as jest.Mock).mockImplementation((audienceId, insights) => { 87 | if (audienceId === 'segment-1') { 88 | return { 89 | insights: [ 90 | { 91 | name: 'bio_keyword', 92 | values: [ 93 | { key: 'tech', value: '30' }, 94 | { key: 'marketing', value: '20' }, 95 | { key: 'design', value: '15' }, 96 | { key: 'developer', value: '10' }, 97 | { key: 'product', value: '5' } 98 | ] 99 | }, 100 | { 101 | name: 'country', 102 | values: [ 103 | { key: 'US', value: '40' }, 104 | { key: 'UK', value: '20' }, 105 | { key: 'Canada', value: '15' }, 106 | { key: 'Germany', value: '10' }, 107 | { key: 'France', value: '5' } 108 | ] 109 | } 110 | ] 111 | }; 112 | } else if (audienceId === 'segment-2') { 113 | return { 114 | insights: [ 115 | { 116 | name: 'bio_keyword', 117 | values: [ 118 | { key: 'finance', value: '35' }, 119 | { key: 'business', value: '25' }, 120 | { key: 'entrepreneur', value: '15' }, 121 | { key: 'investor', value: '10' }, 122 | { key: 'startup', value: '5' } 123 | ] 124 | }, 125 | { 126 | name: 'country', 127 | values: [ 128 | { key: 'US', value: '30' }, 129 | { key: 'India', value: '25' }, 130 | { key: 'UK', value: '15' }, 131 | { key: 'Australia', value: '10' }, 132 | { key: 'Singapore', value: '5' } 133 | ] 134 | } 135 | ] 136 | }; 137 | } 138 | return { insights: [] }; 139 | }); 140 | 141 | // Mock influencers 142 | (compareAudienceInfluencers as jest.Mock).mockImplementation((segmentId, baselineId) => { 143 | if (segmentId === 'segment-1-influencers') { 144 | return { 145 | cursor: { next: 10, prev: 0 }, 146 | influencers: [ 147 | { id: 'inf-1', affinity: 80, baseline_affinity: 30, uniqueness: 50 }, 148 | { id: 'inf-2', affinity: 75, baseline_affinity: 25, uniqueness: 50 } 149 | ] 150 | }; 151 | } else if (segmentId === 'segment-2-influencers') { 152 | return { 153 | cursor: { next: 10, prev: 0 }, 154 | influencers: [ 155 | { id: 'inf-3', affinity: 85, baseline_affinity: 35, uniqueness: 50 }, 156 | { id: 'inf-4', affinity: 70, baseline_affinity: 20, uniqueness: 50 } 157 | ] 158 | }; 159 | } 160 | return { cursor: { next: 0, prev: 0 }, influencers: [] }; 161 | }); 162 | 163 | const result = await generateReportSummary('test-report-id'); 164 | 165 | // Verify the structure of the result 166 | expect(result).toHaveProperty('id', 'test-report-id'); 167 | expect(result).toHaveProperty('title', 'Test Report'); 168 | expect(result).toHaveProperty('segmentation_type', 'test'); 169 | expect(result).toHaveProperty('full_audience_size', 1000); 170 | expect(result).toHaveProperty('segments'); 171 | expect(result?.segments).toHaveLength(2); 172 | 173 | // Check first segment 174 | const segment1 = result?.segments?.[0]; 175 | expect(segment1).toHaveProperty('id', 'segment-1'); 176 | expect(segment1).toHaveProperty('title', 'Segment 1'); 177 | expect(segment1).toHaveProperty('size', 500); 178 | expect(segment1).toHaveProperty('insights'); 179 | expect(segment1).toHaveProperty('influencers'); 180 | 181 | // Verify insights for first segment 182 | expect(segment1?.insights).toHaveProperty('bio_keyword'); 183 | expect(segment1?.insights.bio_keyword?.[0]).toHaveProperty('key', 'tech'); 184 | expect(segment1?.insights.bio_keyword?.[0]).toHaveProperty('value', 30); 185 | 186 | // Verify influencers for first segment 187 | expect(segment1?.influencers?.[0]).toHaveProperty('id', 'inf-1'); 188 | expect(segment1?.influencers?.[0]).toHaveProperty('affinity', 80); 189 | }); 190 | 191 | it('should handle API errors gracefully', async () => { 192 | // Mock a failed API call 193 | (getReportInfo as jest.Mock).mockRejectedValue(new Error('API Error')); 194 | 195 | const result = await generateReportSummary('test-report-id'); 196 | 197 | expect(result).toBeNull(); 198 | }); 199 | }); 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ⚠️ **Deprecated** 2 | 3 | ## 🚫 This repository is no longer maintained. 4 | 5 | >The Audiense Insights MCP has been migrated to a remote model. For more information on how to use the new remote MCP, please reach us at [support@audiense.com](mailto:support@audiense.com). 6 | 7 | --- 8 | --- 9 | 10 | ## 🏆 Audiense Insights MCP Server 11 | 12 | This server, based on the [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol), allows **Claude** or any other MCP-compatible client to interact with your [Audiense Insights](https://www.audiense.com/) account. It extracts **marketing insights and audience analysis** from Audiense reports, covering **demographic, cultural, influencer, and content engagement analysis**. 13 | 14 | 15 | ## 🚀 Prerequisites 16 | 17 | Before using this server, ensure you have: 18 | 19 | - **Node.js** (v18 or higher) 20 | - **Claude Desktop App** 21 | - **Audiense Insights Account** with API credentials 22 | - **X/Twitter API Bearer Token** _(optional, for enriched influencer data)_ 23 | 24 | --- 25 | 26 | ## ⚙️ Configuring Claude Desktop 27 | 28 | 1. Open the configuration file for Claude Desktop: 29 | 30 | - **MacOS:** 31 | ```bash 32 | code ~/Library/Application\ Support/Claude/claude_desktop_config.json 33 | ``` 34 | - **Windows:** 35 | ```bash 36 | code %AppData%\Claude\claude_desktop_config.json 37 | ``` 38 | 39 | 2. Add or update the following configuration: 40 | 41 | ```json 42 | "mcpServers": { 43 | "audiense-insights": { 44 | "command": "npx", 45 | "args": [ 46 | "-y", 47 | "mcp-audiense-insights" 48 | ], 49 | "env": { 50 | "AUDIENSE_CLIENT_ID": "your_client_id_here", 51 | "AUDIENSE_CLIENT_SECRET": "your_client_secret_here", 52 | "TWITTER_BEARER_TOKEN": "your_token_here" 53 | } 54 | } 55 | } 56 | 57 | 3. Save the file and restart Claude Desktop. 58 | 59 | ## 🛠️ Available Tools 60 | ### 📌 `get-reports` 61 | **Description**: Retrieves the list of **Audiense insights reports** owned by the authenticated user. 62 | 63 | - **Parameters**: _None_ 64 | - **Response**: 65 | - List of reports in JSON format. 66 | 67 | --- 68 | 69 | ### 📌 `get-report-info` 70 | **Description**: Fetches detailed information about a **specific intelligence report**, including: 71 | - Status 72 | - Segmentation type 73 | - Audience size 74 | - Segments 75 | - Access links 76 | 77 | - **Parameters**: 78 | - `report_id` _(string)_: The ID of the intelligence report. 79 | 80 | - **Response**: 81 | - Full report details in JSON format. 82 | - If the report is still processing, returns a message indicating the pending status. 83 | 84 | --- 85 | 86 | ### 📌 `get-audience-insights` 87 | **Description**: Retrieves **aggregated insights** for a given **audience**, including: 88 | - **Demographics**: Gender, age, country. 89 | - **Behavioral traits**: Active hours, platform usage. 90 | - **Psychographics**: Personality traits, interests. 91 | - **Socioeconomic factors**: Income, education status. 92 | 93 | - **Parameters**: 94 | - `audience_insights_id` _(string)_: The ID of the audience insights. 95 | - `insights` _(array of strings, optional)_: List of specific insight names to filter. 96 | 97 | - **Response**: 98 | - Insights formatted as a structured text list. 99 | 100 | --- 101 | 102 | ### 📌 `get-baselines` 103 | **Description**: Retrieves available **baseline audiences**, optionally filtered by **country**. 104 | 105 | - **Parameters**: 106 | - `country` _(string, optional)_: ISO country code to filter by. 107 | 108 | - **Response**: 109 | - List of baseline audiences in JSON format. 110 | 111 | --- 112 | 113 | ### 📌 `get-categories` 114 | **Description**: Retrieves the list of **available affinity categories** that can be used in influencer comparisons. 115 | 116 | - **Parameters**: _None_ 117 | - **Response**: 118 | - List of categories in JSON format. 119 | 120 | --- 121 | 122 | ### 📌 `compare-audience-influencers` 123 | **Description**: Compares **influencers** of a given audience with a **baseline audience**. The baseline is determined as follows: 124 | - If a **single country** represents more than 50% of the audience, that country is used as the baseline. 125 | - Otherwise, the **global baseline** is used. 126 | - If a **specific segment** is selected, the full audience is used as the baseline. 127 | 128 | Each influencer comparison includes: 129 | - **Affinity (%)** – How well the influencer aligns with the audience. 130 | - **Baseline Affinity (%)** – The influencer’s affinity within the baseline audience. 131 | - **Uniqueness Score** – How distinct the influencer is compared to the baseline. 132 | 133 | - **Parameters**: 134 | - `audience_influencers_id` _(string)_: ID of the audience influencers. 135 | - `baseline_audience_influencers_id` _(string)_: ID of the baseline audience influencers. 136 | - `cursor` _(number, optional)_: Pagination cursor. 137 | - `count` _(number, optional)_: Number of items per page (default: 200). 138 | - `bio_keyword` _(string, optional)_: Filter influencers by **bio keyword**. 139 | - `entity_type` _(enum: `person` | `brand`, optional)_: Filter by entity type. 140 | - `followers_min` _(number, optional)_: Minimum number of followers. 141 | - `followers_max` _(number, optional)_: Maximum number of followers. 142 | - `categories` _(array of strings, optional)_: Filter influencers by **categories**. 143 | - `countries` _(array of strings, optional)_: Filter influencers by **country ISO codes**. 144 | 145 | - **Response**: 146 | - List of influencers with **affinity scores, baseline comparison, and uniqueness scores** in JSON format. 147 | 148 | --- 149 | 150 | ### 📌 `get-audience-content` 151 | **Description**: Retrieves **audience content engagement details**, including: 152 | - **Liked Content**: Most popular posts, domains, emojis, hashtags, links, media, and a word cloud. 153 | - **Shared Content**: Most shared content categorized similarly. 154 | - **Influential Content**: Content from influential accounts. 155 | 156 | Each category contains: 157 | - `popularPost`: Most engaged posts. 158 | - `topDomains`: Most mentioned domains. 159 | - `topEmojis`: Most used emojis. 160 | - `topHashtags`: Most used hashtags. 161 | - `topLinks`: Most shared links. 162 | - `topMedia`: Shared media. 163 | - `wordcloud`: Most frequently used words. 164 | 165 | - **Parameters**: 166 | - `audience_content_id` _(string)_: The ID of the audience content. 167 | 168 | - **Response**: 169 | - Content engagement data in JSON format. 170 | 171 | --- 172 | 173 | ### 📌 `report-summary` 174 | **Description**: Generates a **comprehensive summary** of an Audiense report, including: 175 | - Report metadata (title, segmentation type) 176 | - Full audience size 177 | - Detailed segment information 178 | - **Top insights** for each segment (bio keywords, demographics, interests) 179 | - **Top influencers** for each segment with comparison metrics 180 | 181 | - **Parameters**: 182 | - `report_id` _(string)_: The ID of the intelligence report to summarize. 183 | 184 | - **Response**: 185 | - Complete report summary in JSON format with structured data for each segment 186 | - For pending reports: Status message indicating the report is still processing 187 | - For reports without segments: Message indicating there are no segments to analyze 188 | 189 | ## 💡 Predefined Prompts 190 | 191 | This server includes a preconfigured prompts 192 | - `audiense-demo`: Helps analyze Audiense reports interactively. 193 | - `segment-matching`: A prompt to match and compare audience segments across Audiense reports, identifying similarities, unique traits, and key insights based on demographics, interests, influencers, and engagement patterns. 194 | 195 | 196 | **Usage:** 197 | - Accepts a reportName argument to find the most relevant report. 198 | - If an ID is provided, it searches by report ID instead. 199 | 200 | Use case: Structured guidance for audience analysis. 201 | 202 | ## 🛠️ Troubleshooting 203 | 204 | ### Tools Not Appearing in Claude 205 | 1. Check Claude Desktop logs: 206 | 207 | ``` 208 | tail -f ~/Library/Logs/Claude/mcp*.log 209 | ``` 210 | 2. Verify environment variables are set correctly. 211 | 3. Ensure the absolute path to index.js is correct. 212 | 213 | ### Authentication Issues 214 | - Double-check OAuth credentials. 215 | - Ensure the refresh token is still valid. 216 | - Verify that the required API scopes are enabled. 217 | 218 | ## 📜 Viewing Logs 219 | 220 | To check server logs: 221 | 222 | ### For MacOS/Linux: 223 | ``` 224 | tail -n 20 -f ~/Library/Logs/Claude/mcp*.log 225 | ``` 226 | 227 | ### For Windows: 228 | ``` 229 | Get-Content -Path "$env:AppData\Claude\Logs\mcp*.log" -Wait -Tail 20 230 | ``` 231 | 232 | ## 🔐 Security Considerations 233 | 234 | - Keep API credentials secure – never expose them in public repositories. 235 | - Use environment variables to manage sensitive data. 236 | 237 | ## 📄 License 238 | 239 | This project is licensed under the Apache 2.0 License. See the LICENSE file for more details. 240 | -------------------------------------------------------------------------------- /src/audienseClient.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import base64 from "base-64"; 3 | import { AuthResponse, IntelligenceReportsResponse, ReportInfoResponse } from "./types.js"; 4 | 5 | const { encode: base64Encode } = base64; 6 | 7 | import { AUDIENSE_API_BASE, CLIENT_ID, CLIENT_SECRET, TWITTER_BEARER_TOKEN, TWITTER_API_BASE } from "./config.js"; 8 | 9 | let accessToken: string | null = null; 10 | let tokenExpiration: number | null = null; 11 | 12 | /** 13 | * Retrieves an OAuth2 access token using client credentials. 14 | * Caches the token until it expires. 15 | */ 16 | async function getAccessToken(): Promise { 17 | if (accessToken && tokenExpiration && Date.now() < tokenExpiration) { 18 | return accessToken; 19 | } 20 | 21 | const authHeader = `Basic ${Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`; 22 | 23 | try { 24 | const response = await fetch(`${AUDIENSE_API_BASE}/login/token`, { 25 | method: "POST", 26 | headers: { 27 | Authorization: authHeader, 28 | "Content-Type": "application/x-www-form-urlencoded", 29 | }, 30 | body: new URLSearchParams({ grant_type: "client_credentials" }).toString(), 31 | }); 32 | 33 | if (!response.ok) { 34 | console.error("Authentication error:", await response.text()); 35 | throw new Error(`Authentication error: ${response.status}`); 36 | } 37 | 38 | const data = (await response.json()) as AuthResponse; 39 | accessToken = data.access_token; 40 | tokenExpiration = Date.now() + data.expires_in * 1000; 41 | 42 | return accessToken; 43 | } catch (error) { 44 | console.error("Error retrieving access token:", error); 45 | return null; 46 | } 47 | } 48 | 49 | /** 50 | * Makes an authenticated request to the Audiense API. 51 | * Automatically retrieves and refreshes the access token if needed. 52 | */ 53 | async function makeAudienseRequest(endpoint: string): Promise { 54 | const token = await getAccessToken(); 55 | if (!token) { 56 | console.error("Failed to retrieve access token."); 57 | return null; 58 | } 59 | 60 | const url = `${AUDIENSE_API_BASE}${endpoint}`; 61 | 62 | try { 63 | const response = await fetch(url, { 64 | method: "GET", 65 | headers: { 66 | Authorization: `Bearer ${token}`, 67 | Accept: "application/json", 68 | }, 69 | }); 70 | 71 | if (!response.ok) { 72 | console.error(`Request error ${response.status} for ${endpoint}:`, await response.text()); 73 | return null; 74 | } 75 | 76 | return (await response.json()) as T; 77 | } catch (error) { 78 | console.error(`Request error for ${endpoint}:`, error); 79 | return null; 80 | } 81 | } 82 | 83 | /** 84 | * Retrieves the list of intelligence reports. 85 | */ 86 | export async function getIntelligenceReports(): Promise { 87 | return makeAudienseRequest("/reports/intelligence"); 88 | } 89 | 90 | /** 91 | * Retrieves details of a specific intelligence report. 92 | */ 93 | export async function getReportInfo(report_id: string): Promise { 94 | return makeAudienseRequest(`/reports/intelligence/${report_id}`); 95 | } 96 | 97 | /** 98 | * Retrieves audience insights for a given audience_insights_id. 99 | * If `insights` is provided, filters by those insights. 100 | */ 101 | export async function getAudienceInsights( 102 | audience_insights_id: string, 103 | insights?: string[] 104 | ): Promise<{ insights: { name: string; values: { key: string; value: string }[] }[] } | null> { 105 | const queryParams = insights ? `?insights=${insights.join(",")}` : ""; 106 | return makeAudienseRequest<{ insights: { name: string; values: { key: string; value: string }[] }[] }>( 107 | `/audience_insights/${audience_insights_id}${queryParams}` 108 | ); 109 | } 110 | 111 | 112 | type TwitterUser = { 113 | id: string; 114 | description?: string; 115 | is_identity_verified?: boolean; 116 | location?: string; 117 | name?: string; 118 | parody?: boolean; 119 | profile_image_url?: string; 120 | protected?: boolean; 121 | public_metrics?: Record; 122 | url?: string; 123 | username?: string; 124 | verified?: boolean; 125 | verified_followers_count?: number; 126 | verified_type?: string; 127 | }; 128 | 129 | type TwitterAPIResponse = { 130 | data?: TwitterUser[]; 131 | }; 132 | 133 | /** 134 | * Fetches Twitter user details for up to 100 user IDs. 135 | */ 136 | async function fetchTwitterUserDetails(userIds: string[]): Promise> { 137 | if (!TWITTER_BEARER_TOKEN) { 138 | console.error("Missing Twitter/X Bearer Token. Skipping Twitter/X enrichment."); 139 | return {}; 140 | } 141 | 142 | const idsParam = userIds.join(","); 143 | const userFields = [ 144 | "description", 145 | "is_identity_verified", 146 | "location", 147 | "name", 148 | "parody", 149 | "profile_image_url", 150 | "protected", 151 | "public_metrics", 152 | "url", 153 | "username", 154 | "verified", 155 | "verified_followers_count", 156 | "verified_type" 157 | ].join(","); 158 | 159 | const url = `${TWITTER_API_BASE}/users?ids=${idsParam}&user.fields=${userFields}`; 160 | 161 | try { 162 | const response = await fetch(url, { 163 | method: "GET", 164 | headers: { 165 | Authorization: `Bearer ${TWITTER_BEARER_TOKEN}`, 166 | "Content-Type": "application/json", 167 | }, 168 | }); 169 | 170 | if (!response.ok) { 171 | console.error("Twitter API error:", await response.text()); 172 | return {}; 173 | } 174 | 175 | const data = (await response.json()) as TwitterAPIResponse; 176 | 177 | if (!data.data) return {}; 178 | 179 | return Object.fromEntries(data.data.map((user) => [user.id, user])); 180 | } catch (error) { 181 | console.error("Error fetching Twitter user details:", error); 182 | return {}; 183 | } 184 | } 185 | 186 | /** 187 | * Compares the influencers of an audience with those of a baseline, enriched with Twitter user details. 188 | */ 189 | export async function compareAudienceInfluencers( 190 | audience_influencers_id: string, 191 | baseline_audience_influencers_id: string, 192 | cursor?: number, 193 | count?: number, 194 | bio_keyword?: string, 195 | entity_type?: "person" | "brand", 196 | followers_min?: number, 197 | followers_max?: number, 198 | categories?: string[], 199 | countries?: string[] 200 | ): Promise<{ cursor: { next: number; prev: number }; influencers: any[] } | null> { 201 | const queryParams = new URLSearchParams(); 202 | 203 | if (cursor !== undefined) queryParams.append("cursor", cursor.toString()); 204 | if (count !== undefined) queryParams.append("count", count.toString()); 205 | if (bio_keyword) queryParams.append("bio_keyword", bio_keyword); 206 | if (entity_type) queryParams.append("entity_type", entity_type); 207 | if (followers_min !== undefined) queryParams.append("followers_min", followers_min.toString()); 208 | if (followers_max !== undefined) queryParams.append("followers_max", followers_max.toString()); 209 | if (categories && categories.length > 0) queryParams.append("categories", categories.join(",")); 210 | if (countries && countries.length > 0) queryParams.append("countries", countries.join(",")); 211 | 212 | const endpoint = `/audience_influencers/${audience_influencers_id}/compared_to/${baseline_audience_influencers_id}?${queryParams.toString()}`; 213 | 214 | const data = await makeAudienseRequest<{ cursor: { next: number; prev: number }; influencers: { id: string; affinity: number; baseline_affinity: number; uniqueness: number }[] }>(endpoint); 215 | 216 | if (!data || !data.influencers.length) { 217 | return data; 218 | } 219 | 220 | // Get the first 100 influencers for enrichment 221 | const influencerIds = data.influencers.slice(0, 100).map((influencer) => influencer.id); 222 | const twitterData = await fetchTwitterUserDetails(influencerIds); 223 | 224 | // Merge Twitter details into influencer data 225 | const enrichedInfluencers = data.influencers.map((influencer) => ({ 226 | ...influencer, 227 | twitter: twitterData[influencer.id] || null, // Add Twitter data if available 228 | })); 229 | 230 | return { 231 | cursor: data.cursor, 232 | influencers: enrichedInfluencers, 233 | }; 234 | } 235 | 236 | /** 237 | * Retrieves the relevant content that an audience engages with. 238 | */ 239 | export async function getAudienceContent(audience_content_id: string): Promise<{ 240 | createdAt: string; 241 | startDate: string; 242 | endDate: string; 243 | status: string; 244 | likedContent: any; 245 | sharedContent: any; 246 | influentialContent: any; 247 | } | null> { 248 | return makeAudienseRequest<{ 249 | createdAt: string; 250 | startDate: string; 251 | endDate: string; 252 | status: string; 253 | likedContent: any; 254 | sharedContent: any; 255 | influentialContent: any; 256 | }>(`/audience_content/${audience_content_id}`); 257 | } 258 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 5 | import { z } from "zod"; 6 | import { getIntelligenceReports, getReportInfo, getAudienceInsights, compareAudienceInfluencers, getAudienceContent } from "./audienseClient.js"; 7 | import { BASELINES } from "./baselines.js"; 8 | import { CATEGORIES } from "./categories.js"; 9 | import { DEMO_PROMPT, DEMO_PROMPT2, SEGMENT_MATCHING_PROMPT } from "./promts.js"; 10 | import { generateReportSummary } from "./reportSummary.js"; 11 | 12 | // MCP Server instance 13 | const server = new McpServer({ 14 | name: "audiense-insights", 15 | version: "1.0.0", 16 | }); 17 | 18 | /** 19 | * MCP Tool: Fetches a list of intelligence reports for the authenticated user. 20 | */ 21 | server.tool( 22 | "get-reports", 23 | "Retrieves the list of Audiense insights reports owned by the authenticated user.", 24 | {}, 25 | async () => { 26 | const data = await getIntelligenceReports(); 27 | 28 | if (!data) { 29 | return { 30 | content: [ 31 | { 32 | type: "text", 33 | text: "Failed to retrieve intelligence reports.", 34 | }, 35 | ], 36 | }; 37 | } 38 | 39 | return { 40 | content: [ 41 | { 42 | type: "text", 43 | text: JSON.stringify(data, null, 2) 44 | }, 45 | ], 46 | }; 47 | } 48 | ); 49 | 50 | /** 51 | * MCP Tool: Fetches details of a specific intelligence report. 52 | */ 53 | server.tool( 54 | "get-report-info", 55 | "Retrieves detailed information about a specific intelligence report, including its status, segmentation type, audience size, segments, and access links.", 56 | { 57 | report_id: z.string().describe("The ID of the intelligence report."), 58 | }, 59 | async ({ report_id }) => { 60 | const data = await getReportInfo(report_id); 61 | 62 | if (!data) { 63 | return { 64 | content: [ 65 | { 66 | type: "text", 67 | text: `Failed to retrieve report info for ID: ${report_id}.`, 68 | }, 69 | ], 70 | }; 71 | } 72 | 73 | if (data.status === "pending") { 74 | return { 75 | content: [ 76 | { 77 | type: "text", 78 | text: `Report ${report_id} is still processing. Try again later.`, 79 | }, 80 | ], 81 | }; 82 | } 83 | 84 | return { 85 | content: [ 86 | { 87 | type: "text", 88 | text: JSON.stringify(data, null, 2) 89 | } 90 | ] 91 | }; 92 | } 93 | ); 94 | 95 | /** 96 | * MCP Tool: Fetches aggregated insights for a given audience ID. 97 | */ 98 | server.tool( 99 | "get-audience-insights", 100 | `Retrieves aggregated insights for a given audience ID, providing statistical distributions across various attributes. 101 | Available insights include demographics (e.g., gender, age, country), behavioral traits (e.g., active hours, platform usage), psychographics (e.g., personality traits, interests), and socioeconomic factors (e.g., income, education status).`, 102 | { 103 | audience_insights_id: z.string().describe("The ID of the audience insights."), 104 | insights: z.array(z.string()).optional().describe("Optional list of insight names to filter."), 105 | }, 106 | async ({ audience_insights_id, insights }) => { 107 | const data = await getAudienceInsights(audience_insights_id, insights); 108 | 109 | if (!data || !data.insights.length) { 110 | return { 111 | content: [ 112 | { 113 | type: "text", 114 | text: `No insights found for audience ${audience_insights_id}.`, 115 | }, 116 | ], 117 | }; 118 | } 119 | 120 | const insightsText = data.insights 121 | .map( 122 | (insight) => 123 | `**${insight.name}**:\n${insight.values 124 | .map((val) => `- ${val.key}: ${val.value}%`) 125 | .join("\n")}` 126 | ) 127 | .join("\n\n"); 128 | 129 | return { 130 | content: [ 131 | { 132 | type: "text", 133 | text: `Audience Insights for ${audience_insights_id}:\n\n${insightsText}`, 134 | }, 135 | ], 136 | }; 137 | } 138 | ); 139 | 140 | server.tool( 141 | "get-baselines", 142 | "Retrieves available baselines, optionally filtered by country.", 143 | { 144 | country: z.string().optional().describe("ISO country code to filter by.") 145 | }, 146 | async ({ country }) => { 147 | let filteredBaselines = BASELINES; 148 | 149 | // Apply country filter if provided 150 | if (country) { 151 | filteredBaselines = filteredBaselines.filter( 152 | (baseline) => baseline.country_iso_code === country 153 | ); 154 | } 155 | 156 | if (filteredBaselines.length === 0) { 157 | return { 158 | content: [ 159 | { 160 | type: "text", 161 | text: `No baselines found for the given filters.`, 162 | }, 163 | ], 164 | }; 165 | } 166 | 167 | return { 168 | content: [ 169 | { 170 | type: "text", 171 | text: JSON.stringify(filteredBaselines, null, 2), 172 | }, 173 | ], 174 | }; 175 | } 176 | ); 177 | 178 | server.tool( 179 | "get-categories", 180 | "Retrieves the list of available affinity categories that can be used as the categories parameter in the compare-audience-influencers tool.", 181 | {}, 182 | async () => { 183 | return { 184 | content: [ 185 | { 186 | type: "text", 187 | text: JSON.stringify(CATEGORIES, null, 2), 188 | }, 189 | ], 190 | }; 191 | } 192 | ); 193 | 194 | server.tool( 195 | "compare-audience-influencers", 196 | `Compares the influencers of an audience with a baseline audience. The baseline is determined as follows: 197 | If the selection was the full audience and a single country represents more than 50% of the audience, that country is used as the baseline. 198 | Otherwise, the Global baseline is applied. If the selection was a specific segment, the full audience is used as the baseline. 199 | Each influencer comparison includes: 200 | - Affinity (%) - The level of alignment between the influencer and the audience. Baseline Affinity (%) 201 | - The influencer’s affinity within the baseline audience. Uniqueness Score 202 | - A measure of how distinct the influencer is within the selected audience compared to the baseline. 203 | `, 204 | { 205 | audience_influencers_id: z.string().describe("The ID of the audience influencers."), 206 | baseline_audience_influencers_id: z.string().describe("The ID of the baseline audience influencers."), 207 | cursor: z.number().optional().describe("Cursor for pagination."), 208 | count: z.number().optional().describe("Number of items per page (default: 200)."), 209 | bio_keyword: z.string().optional().describe("Keyword to filter influencers by their biography."), 210 | entity_type: z.enum(["person", "brand"]).optional().describe("Filter by entity type (person or brand)."), 211 | followers_min: z.number().optional().describe("Minimum number of followers."), 212 | followers_max: z.number().optional().describe("Maximum number of followers."), 213 | categories: z.array(z.string()).optional().describe("Filter influencers by categories."), 214 | countries: z.array(z.string()).optional().describe("Filter influencers by country ISO codes."), 215 | }, 216 | async ({ audience_influencers_id, baseline_audience_influencers_id, cursor, count, bio_keyword, entity_type, followers_min, followers_max, categories, countries }) => { 217 | const data = await compareAudienceInfluencers( 218 | audience_influencers_id, 219 | baseline_audience_influencers_id, 220 | cursor, 221 | count, 222 | bio_keyword, 223 | entity_type, 224 | followers_min, 225 | followers_max, 226 | categories, 227 | countries 228 | ); 229 | 230 | if (!data || !data.influencers.length) { 231 | return { 232 | content: [ 233 | { 234 | type: "text", 235 | text: `No influencers found for comparison between ${audience_influencers_id} and ${baseline_audience_influencers_id}.`, 236 | }, 237 | ], 238 | }; 239 | } 240 | 241 | const influencersText = data.influencers 242 | .map( 243 | (influencer) => 244 | `ID: ${influencer.id}\nAffinity: ${influencer.affinity}%\nBaseline Affinity: ${influencer.baseline_affinity}%\nUniqueness: ${influencer.uniqueness}%` 245 | ) 246 | .join("\n\n"); 247 | 248 | return { 249 | content: [ 250 | { 251 | type: "text", 252 | text: JSON.stringify(data, null, 2), 253 | }, 254 | ], 255 | }; 256 | } 257 | ); 258 | 259 | server.tool( 260 | "get-audience-content", 261 | `Retrieves audience content engagement details for a given audience. 262 | 263 | This tool provides a detailed breakdown of the content an audience interacts with, including: 264 | - **Liked Content**: Popular posts, top domains, top emojis, top hashtags, top links, top media, and a word cloud. 265 | - **Shared Content**: Content that the audience shares, categorized similarly to liked content. 266 | - **Influential Content**: Content from influential accounts that impact the audience, with similar categorization. 267 | 268 | Each category contains: 269 | - **popularPost**: List of the most engaged posts. 270 | - **topDomains**: Most mentioned domains. 271 | - **topEmojis**: Most used emojis. 272 | - **topHashtags**: Most used hashtags. 273 | - **topLinks**: Most shared links. 274 | - **topMedia**: Media types shared and samples. 275 | - **wordcloud**: Frequently used words.`, 276 | { 277 | audience_content_id: z.string().describe("The ID of the audience content to retrieve."), 278 | }, 279 | async ({ audience_content_id }) => { 280 | const data = await getAudienceContent(audience_content_id); 281 | 282 | if (!data) { 283 | return { 284 | content: [ 285 | { 286 | type: "text", 287 | text: `No content found for audience ${audience_content_id}.`, 288 | }, 289 | ], 290 | }; 291 | } 292 | 293 | return { 294 | content: [ 295 | { 296 | type: "text", 297 | text: JSON.stringify(data, null, 2), 298 | }, 299 | ], 300 | }; 301 | } 302 | ); 303 | 304 | 305 | server.prompt( 306 | "audiense-demo", 307 | "A prompt to extract marketing insights and audience understanding from Audiense reports through demographic, cultural, influencer, and content analysis.", 308 | { reportName: z.string().describe("The name or id of the Audiense Insights report.") }, 309 | ({ reportName }) => ({ 310 | messages: [{ 311 | role: "user", 312 | content: { 313 | type: "text", 314 | text: DEMO_PROMPT.replaceAll('{reportName}', reportName) 315 | } 316 | }] 317 | }) 318 | ); 319 | 320 | server.prompt( 321 | "audiense-demo2", 322 | "A prompt to extract marketing insights and audience understanding from Audiense reports through demographic, cultural, influencer, and content analysis.", 323 | { reportName: z.string().describe("The name or id of the Audiense Insights report.") }, 324 | ({ reportName }) => ({ 325 | messages: [{ 326 | role: "user", 327 | content: { 328 | type: "text", 329 | text: DEMO_PROMPT2.replaceAll('{reportName}', reportName) 330 | } 331 | }] 332 | }) 333 | ); 334 | 335 | server.prompt( 336 | "segment-matching", 337 | "A prompt to match and compare audience segments across Audiense reports, identifying similarities, unique traits, and key insights based on demographics, interests, influencers, and engagement patterns.", 338 | { 339 | brand1: z.string().describe("The name or ID of the Audiense Insights report for the first brand to analyze."), 340 | brand2: z.string().describe("The name or ID of the Audiense Insights report for the second brand to analyze.") 341 | }, 342 | ({ brand1, brand2 }) => ({ 343 | messages: [{ 344 | role: "user", 345 | content: { 346 | type: "text", 347 | text: SEGMENT_MATCHING_PROMPT 348 | .replaceAll('{brand1}', brand1) 349 | .replaceAll('{brand2}', brand2) 350 | } 351 | }] 352 | }) 353 | ); 354 | 355 | /** 356 | * MCP Tool: Generates a comprehensive summary of an Audiense report 357 | */ 358 | server.tool( 359 | "report-summary", 360 | "Generates a comprehensive summary of an Audiense report, including segment details, top insights, and influencers.", 361 | { 362 | report_id: z.string().describe("The ID of the intelligence report to summarize."), 363 | }, 364 | async ({ report_id }) => { 365 | const data = await generateReportSummary(report_id); 366 | 367 | if (!data) { 368 | return { 369 | content: [ 370 | { 371 | type: "text", 372 | text: `Failed to generate summary for report ID: ${report_id}.`, 373 | }, 374 | ], 375 | }; 376 | } 377 | 378 | if (data.message) { 379 | return { 380 | content: [ 381 | { 382 | type: "text", 383 | text: data.message, 384 | }, 385 | ], 386 | }; 387 | } 388 | 389 | return { 390 | content: [ 391 | { 392 | type: "text", 393 | text: JSON.stringify(data, null, 2) 394 | } 395 | ] 396 | }; 397 | } 398 | ); 399 | 400 | /** 401 | * Starts the MCP server and listens for incoming requests. 402 | */ 403 | async function runServer() { 404 | const transport = new StdioServerTransport(); 405 | await server.connect(transport); 406 | console.error("Audiense Insights MCP Server running on stdio"); 407 | } 408 | 409 | runServer().catch((error) => { 410 | console.error("Fatal error in main():", error); 411 | process.exit(1); 412 | }); 413 | -------------------------------------------------------------------------------- /src/promts.ts: -------------------------------------------------------------------------------- 1 | export const DEMO_PROMPT = ` 2 | Audiense Insights is a platform that helps identify, understand, and activate valuable audience segments through social data analysis. 3 | 4 | This text file contains instructions for how to analyze audience reports using the Audiense Insights API. The goal is to help users discover meaningful cultural and behavioral insights about their audiences to drive better marketing decisions. 5 | 6 | The user has already selected the MCP integration within the paperclip menu and chosen 'audiense-demo' from the available prompts. They have provided a report name parameter: {reportName}, which we'll use to find the relevant report for analysis. 7 | 8 | 9 | Audiense Insights Documentation: 10 | 11 | Core Features: 12 | 1. Audience Identification & Segmentation 13 | - Create audiences based on 175+ profile attributes 14 | - Filter by age, bio keywords, followers, gender, interests, job title, language, location 15 | - Analyze conversations around keywords/hashtags 16 | - Import audiences from Social Listening or CRM 17 | 18 | 2. Segmentation Types: 19 | - Affinity Segmentation: For large audiences, creates broader segments based on shared interests 20 | - Interconnectivity Segmentation: For niche audiences, identifies interconnected communities and potential outliers 21 | 22 | 3. Audience Insights Categories: 23 | - Demographics: Gender, location, languages, bio keywords, names, age ranges 24 | - Socioeconomics: Education, job industry, relationship status, family status, household income (US only) 25 | - Interests: Categorized by IAB, Podcasts, Film genres, Music Genres, Industry topics 26 | - Media Affinity: Radio, Newspapers, Magazines, Events, Places, Digital content, Apps 27 | - Personality Insights: IBM Watson-powered analysis of personality traits and buying mindsets 28 | - Content Engagement: Most liked/shared tweets, articles, hashtags, media from last 30 days 29 | - Online Habits: Social media channel preferences, posting times, device usage, engagement rates 30 | 31 | Note that this integration doesn't allow to create reports but this documentation is important to understand how reports that we are going to analyze were generated. 32 | 33 | 34 | 35 | Tools: 36 | This server provides several tools to interact with the Audiense Insights APIs: 37 | 38 | "get-reports": 39 | - Lists all intelligence reports available for the authenticated user 40 | - No parameters required 41 | - Use this as your starting point to discover available audience analyses 42 | 43 | "get-report-info": 44 | - Retrieves detailed information about a specific intelligence report, including its status, segmentation type, audience size, segments, and access links. 45 | - Required parameter: report_id (string) 46 | - Use this to understand the scope and segments of a particular audience report 47 | 48 | "get-audience-insights": 49 | - Retrieves aggregated insights for a given audience ID, providing statistical distributions across various attributes. 50 | Available insights include demographics (e.g., gender, age, country), behavioral traits (e.g., active hours, platform usage), psychographics (e.g., personality traits, interests), and socioeconomic factors (e.g., income, education status). 51 | - Required parameter: audience_insights_id (string) 52 | - Optional parameter: insights (array of strings) to filter specific insight types 53 | - Returns detailed data about audience characteristics and preferences 54 | 55 | "compare-audience-influencers": 56 | - Compares the influencers of an audience with a baseline audience. The baseline is determined as follows: 57 | If the selection was the full audience and a single country represents more than 50% of the audience, that country is used as the baseline. 58 | Otherwise, the Global baseline is applied. If the selection was a specific segment, the full audience is used as the baseline. 59 | Each influencer comparison includes: 60 | - Affinity (%) - The level of alignment between the influencer and the audience. Baseline Affinity (%) 61 | - The influencer’s affinity within the baseline audience. Uniqueness Score 62 | - A measure of how distinct the influencer is within the selected audience compared to the baseline. 63 | - Required parameters: 64 | * audience_influencers_id (string) 65 | * baseline_audience_influencers_id (string) 66 | - Optional filtering parameters: 67 | * cursor (number) for pagination 68 | * count (number) for results per page (default: 200) 69 | * bio_keyword (string) to filter by biography content 70 | * entity_type (person/brand) 71 | * followers_min/max (numbers) 72 | * categories (array of strings) 73 | * countries (array of strings) 74 | 75 | "get-audience-content": 76 | - Retrieves audience content engagement details for a given audience. 77 | 78 | This tool provides a detailed breakdown of the content an audience interacts with, including: 79 | - **Liked Content**: Popular posts, top domains, top emojis, top hashtags, top links, top media, and a word cloud. 80 | - **Shared Content**: Content that the audience shares, categorized similarly to liked content. 81 | - **Influential Content**: Content from influential accounts that impact the audience, with similar categorization. 82 | 83 | Each category contains: 84 | - **popularPost**: List of the most engaged posts. 85 | - **topDomains**: Most mentioned domains. 86 | - **topEmojis**: Most used emojis. 87 | - **topHashtags**: Most used hashtags. 88 | - **topLinks**: Most shared links. 89 | - **topMedia**: Media types shared and samples. 90 | - **wordcloud**: Frequently used words 91 | - Required parameter: audience_content_id (string) 92 | 93 | "get-baselines": 94 | - Retrieves available baselines, optionally filtered by country. 95 | - Optional parameter: country (string) - ISO country code to filter by. 96 | - Returns a list of baselines relevant to the audience analysis. 97 | - Use this to determine the most appropriate baseline for comparisons. 98 | 99 | "get-categories": 100 | - Retrieves the list of available affinity categories that can be used as the categories parameter in the compare-audience-influencers tool. 101 | - No parameters required. 102 | - Returns a list of categories that help in filtering and refining influencer comparisons. 103 | 104 | Prompts: 105 | This server provides a pre-written prompt called "audiense-demo" that helps users analyze Audiense Insights reports. The prompt accepts a "reportName" argument and guides users through analyzing the insights to solve their challenges. For example, if a user provides "Nike Followers" as the report name, the prompt will search for the most recent report that matches the name, if an id is provided it will search by report id instead. Prompts basically serve as interactive templates that help structure the conversation with the LLM in a useful way. 106 | 107 | 108 | 109 | Common use cases for Audiense analysis: 110 | - Develop data-driven personas based on real audience affinities and behaviors 111 | - Segment audiences by interests rather than just demographics 112 | - Identify the right influencers who truly align with their ideal audience 113 | - Understand niche audiences through deep cultural insights 114 | - Create more personalized, targeted marketing strategies 115 | - Analyze audience psychographics, content preferences, and sources of influence 116 | - Compare different audience segments with baselines or competitors 117 | - Generate creative content ideas that resonate with audience interests and culture 118 | - Discover unique content opportunities by connecting audience affinities 119 | - Identify the best platforms and formats for content based on audience engagement patterns 120 | - Create culturally relevant campaigns that tap into audience passions and shared interests 121 | 122 | 123 | 124 | 1. Find the relevant report: 125 | a. Use get-reports to list all available reports 126 | b. Search for the most recent report matching {reportName}, if a report id is provided it will search by report id instead 127 | c. Store the report_id for subsequent analysis 128 | d. Use get-report-info to understand the report's metadata (some ids will be useful to use as parameter for other tools), the scope and segments 129 | 130 | 2. Pause for user input: 131 | a. Ask the user which of the above use cases they want to explore: 132 | - Develop data-driven personas based on real audience affinities and behaviors 133 | - Understand niche audiences through deep cultural insights 134 | - Identify the right influencers who truly align with their ideal audience 135 | - Generate creative content ideas that resonate with audience interests and culture 136 | b. Present multiple choice options for deeper analysis 137 | c. Wait for user selection before proceeding 138 | 139 | 3. Pause for user input: 140 | a. Ask the user if they want to focus the analysis on the full audience or on a specific segment 141 | b. Present multiple choice options for deeper analysis including the full audience and the report segments 142 | c. Wait for user selection before proceeding 143 | 144 | 4. Depending on the use case selection follow a different path 145 | 4.1 Develop data-driven personas based on real audience affinities and behaviors 146 | a. Use get-audience-insights to identify relevant traits and uncover cultural patterns 147 | b. Use get-audience-content to understand the content the audience shared 148 | c. Decide the baseline to use, if selection was full audience and there is a country with more than %50, try to use that country as baseline, else use the Global baseline, use get-baselines to get the baseline id. If the selection was a specific segment use the full audience as the baseline. 149 | d. Use compare-audience-influencers to look for unique affinities and shared interests that define the audience 150 | e. Elaborate a buyer persona and present it using appropriate visualizations and artifacts 151 | 152 | 4.2 Understand niche audiences through deep cultural insights 153 | a. Use get-audience-insights to identify relevant traits and uncover cultural patterns 154 | b. Use get-audience-content to understand the content the audience shared 155 | c. Decide the baseline to use, if selection was full audience and there is a country with more than %50, try to use that country as baseline, else use the Global baseline, use get-baselines to get the baseline id. If the selection was a specific segment use the full audience as the baseline. 156 | d. Use compare-audience-influencers to look for unique affinities and shared interests that define the audience 157 | e. Summarize the most relevant aspect for deep cultural insights 158 | f. Based on the actual report context and data, frame a challenge that requires deep audience understanding 159 | g. Include a brand or marketing professional (the user) who needs to go beyond basic demographics 160 | h. Highlight the need to understand this specific audience's affinities and cultural connections 161 | i. Mention specific goals relevant to the report's audience 162 | j. Present findings using appropriate visualizations and artifacts 163 | 164 | 165 | 4.3 Identify the right influencers who truly align with their ideal audience 166 | a. Use get-audience-insights to identify relevant traits and uncover cultural patterns 167 | b. Decide the baseline to use, if selection was full audience and there is a country with more than %50, try to use that country as baseline, else use the Global baseline, use get-baselines to get the baseline id. If the selection was a specific segment use the full audience as the baseline. 168 | c. Use compare-audience-influencers to look for unique affinities and shared interests that define the audience 169 | d. Use compare-audience-influencers with different followers_max parameters to get micro influencers (for example 100000) and nano influencers (for example 20000) 170 | e. Use get-categories to get the possible categories to filter the influencers 171 | f. Use the most relevant 3 categories for the audience and get the top 5 influencers for each 172 | g. Use Journalist category to get journalist influencers 173 | h. Use Podcasts category to get podcast influencers 174 | i. Present findings using appropriate visualizations and artifacts 175 | 176 | 4.4 Generate creative content ideas that resonate with audience interests and culture 177 | a. Based on user choice, dive deep into specific aspects: 178 | - For influencer alignment: use compare-audience-influencers to find authentic partners 179 | - For content strategy: use get-audience-content to understand what resonates 180 | - For persona development: analyze psychographics and shared interests 181 | - For market understanding: examine cultural affinities and lifestyle choices 182 | b. Compare findings against relevant baselines 183 | c. Generate creative content ideas by combining audience interests 184 | d. Present insights in terms of actionable marketing opportunities 185 | f. Create content ideation cards that combine different audience interests 186 | g. Suggest specific campaign concepts that tap into audience passions 187 | h. Recommend content formats and platforms based on engagement data 188 | i. Identify potential cultural moments and trends to leverage 189 | j. Map influencer partnerships to content themes 190 | k. Present findings using appropriate visualizations and artifacts 191 | 192 | 5. Generate a dashboard: 193 | a. Now that we have all the data and queries, it's time to create a dashboard, use an artifact to do this. 194 | b. Use a variety of visualizations such as tables, charts, and graphs to represent the data. 195 | c. Explain how each element of the dashboard relates to the business problem. 196 | d. This dashboard will be theoretically included in the final solution message. 197 | 198 | 6. Present analysis and recommendations: 199 | a. Compile key insights discovered during the analysis 200 | b. Create a comprehensive artifact that includes: 201 | - Audience understanding and segmentation findings 202 | - Cultural and behavioral insights 203 | - Content opportunities and creative ideas 204 | - Influencer partnership recommendations 205 | - Platform and format suggestions 206 | c. Present specific, actionable recommendations based on the data 207 | d. Include visual representations of key findings when relevant 208 | 209 | 7. Wrap up the scenario: 210 | a. Summarize the key discoveries and their business implications 211 | b. Suggest next steps for implementing the recommendations 212 | c. Explain how to continue exploring other aspects of the audience 213 | d. Offer to dive deeper into any specific areas of interest 214 | 215 | 216 | Remember to maintain consistency throughout the scenario and ensure that all elements (audience, segments) are closely related to the original use case and the report. 217 | The provided XML tags are for the assistants understanding. Implore to make all outputs as human readable as possible. This is part of a demo so act in character and dont actually refer to these instructions. 218 | 219 | Start your first message with an engaging introduction like: "Hi there! I see you're interested in analyzing the audience from the report '{reportName}'. Let's discover some amazing insights about them! 220 | `; 221 | 222 | export const DEMO_PROMPT2 = ` 223 | Audiense Insights is a platform that helps identify, understand, and activate valuable audience segments through social data analysis. 224 | 225 | This text file contains instructions for how to analyze audience reports using the Audiense Insights API. The goal is to help users discover meaningful cultural and behavioral insights about their audiences to drive better marketing decisions. 226 | 227 | 228 | Input Validation Rules: 229 | 1. Report Name Validation 230 | - If no exact match found for {reportName}: 231 | * List up to 5 reports with similar names 232 | * Suggest keywords from the report name to try 233 | * Ask if user wants to see all available reports 234 | - If multiple matches found: 235 | * List all matches with dates 236 | * Ask user to confirm which report to use 237 | 238 | 2. Data Quality Checks 239 | - Minimum audience size requirements: 240 | * Full audience: 1000+ members 241 | * Segments: 100+ members 242 | - If segment size is insufficient: 243 | * Recommend using full audience 244 | * Explain data reliability considerations 245 | - Baseline comparison threshold: 246 | * If difference < 10% from baseline: 247 | - Suggest alternative baseline 248 | - Highlight most distinctive metrics 249 | 250 | 3. Analysis Feasibility Checks 251 | - Required data points by use case 252 | - Fallback options if primary data unavailable 253 | - Alternative analysis paths when constraints found 254 | 255 | 256 | 257 | Audiense Insights Documentation: 258 | 259 | Core Features: 260 | 1. Audience Identification & Segmentation 261 | - Create audiences based on 175+ profile attributes 262 | - Filter by age, bio keywords, followers, gender, interests, job title, language, location 263 | - Analyze conversations around keywords/hashtags 264 | - Import audiences from Social Listening or CRM 265 | 266 | 2. Segmentation Types: 267 | - Affinity Segmentation: For large audiences, creates broader segments based on shared interests 268 | - Interconnectivity Segmentation: For niche audiences, identifies interconnected communities and potential outliers 269 | 270 | 3. Audience Insights Categories: 271 | - Demographics: Gender, location, languages, bio keywords, names, age ranges 272 | - Socioeconomics: Education, job industry, relationship status, family status, household income (US only) 273 | - Interests: Categorized by IAB, Podcasts, Film genres, Music Genres, Industry topics 274 | - Media Affinity: Radio, Newspapers, Magazines, Events, Places, Digital content, Apps 275 | - Personality Insights: IBM Watson-powered analysis of personality traits and buying mindsets 276 | - Content Engagement: Most liked/shared tweets, articles, hashtags, media from last 30 days 277 | - Online Habits: Social media channel preferences, posting times, device usage, engagement rates 278 | 279 | Note that this integration doesn't allow to create reports but this documentation is important to understand how reports that we are going to analyze were generated. 280 | 281 | 282 | 283 | Tools: 284 | This server provides several tools to interact with the Audiense Insights APIs: 285 | 286 | "get-reports": 287 | - Lists all intelligence reports available for the authenticated user 288 | - No parameters required 289 | - Use this as your starting point to discover available audience analyses 290 | 291 | "get-report-info": 292 | - Retrieves detailed information about a specific intelligence report, including its status, segmentation type, audience size, segments, and access links. 293 | - Required parameter: report_id (string) 294 | - Use this to understand the scope and segments of a particular audience report 295 | 296 | "get-audience-insights": 297 | - Retrieves aggregated insights for a given audience ID, providing statistical distributions across various attributes. 298 | Available insights include demographics (e.g., gender, age, country), behavioral traits (e.g., active hours, platform usage), psychographics (e.g., personality traits, interests), and socioeconomic factors (e.g., income, education status). 299 | - Required parameter: audience_insights_id (string) 300 | - Optional parameter: insights (array of strings) to filter specific insight types 301 | - Returns detailed data about audience characteristics and preferences 302 | 303 | "compare-audience-influencers": 304 | - Compares the influencers of an audience with a baseline audience. The baseline is determined as follows: 305 | If the selection was the full audience and a single country represents more than 50% of the audience, that country is used as the baseline. 306 | Otherwise, the Global baseline is applied. If the selection was a specific segment, the full audience is used as the baseline. 307 | Each influencer comparison includes: 308 | - Affinity (%) - The level of alignment between the influencer and the audience. Baseline Affinity (%) 309 | - The influencer’s affinity within the baseline audience. Uniqueness Score 310 | - A measure of how distinct the influencer is within the selected audience compared to the baseline. 311 | - Required parameters: 312 | * audience_influencers_id (string) 313 | * baseline_audience_influencers_id (string) 314 | - Optional filtering parameters: 315 | * cursor (number) for pagination 316 | * count (number) for results per page (default: 200) 317 | * bio_keyword (string) to filter by biography content 318 | * entity_type (person/brand) 319 | * followers_min/max (numbers) 320 | * categories (array of strings) 321 | * countries (array of strings) 322 | 323 | "get-audience-content": 324 | - Retrieves audience content engagement details for a given audience. 325 | 326 | This tool provides a detailed breakdown of the content an audience interacts with, including: 327 | - **Liked Content**: Popular posts, top domains, top emojis, top hashtags, top links, top media, and a word cloud. 328 | - **Shared Content**: Content that the audience shares, categorized similarly to liked content. 329 | - **Influential Content**: Content from influential accounts that impact the audience, with similar categorization. 330 | 331 | Each category contains: 332 | - **popularPost**: List of the most engaged posts. 333 | - **topDomains**: Most mentioned domains. 334 | - **topEmojis**: Most used emojis. 335 | - **topHashtags**: Most used hashtags. 336 | - **topLinks**: Most shared links. 337 | - **topMedia**: Media types shared and samples. 338 | - **wordcloud**: Frequently used words 339 | - Required parameter: audience_content_id (string) 340 | 341 | "get-baselines": 342 | - Retrieves available baselines, optionally filtered by country. 343 | - Optional parameter: country (string) - ISO country code to filter by. 344 | - Returns a list of baselines relevant to the audience analysis. 345 | - Use this to determine the most appropriate baseline for comparisons. 346 | 347 | "get-categories": 348 | - Retrieves the list of available affinity categories that can be used as the categories parameter in the compare-audience-influencers tool. 349 | - No parameters required. 350 | - Returns a list of categories that help in filtering and refining influencer comparisons. 351 | 352 | Prompts: 353 | This server provides a pre-written prompt called "audiense-demo" that helps users analyze Audiense Insights reports. The prompt accepts a "reportName" argument and guides users through analyzing the insights to solve their challenges. For example, if a user provides "Nike Followers" as the report name, the prompt will search for the most recent report that matches the name, if an id is provided it will search by report id instead. 354 | 355 | 356 | 357 | Primary Analysis Paths: 358 | 359 | 1. Build Comprehensive Audience Personas 360 | Goal: Create data-driven personas that combine demographic, behavioral, and cultural insights 361 | Deliverables: 362 | - Detailed persona profiles 363 | - Behavioral patterns and preferences 364 | - Cultural affinities and values 365 | - Content consumption habits 366 | - Platform usage patterns 367 | 368 | 2. Find & Validate Influencers 369 | Goal: Identify and verify authentic influencers who truly resonate with the audience 370 | Deliverables: 371 | - Ranked influencer recommendations 372 | - Audience overlap analysis 373 | - Content performance metrics 374 | - Category-specific insights 375 | - Partnership opportunity assessment 376 | 377 | 3. Develop Content & Channel Strategy 378 | Goal: Create an actionable content strategy based on audience preferences and behaviors 379 | Deliverables: 380 | - Content themes and formats 381 | - Platform recommendations 382 | - Timing and frequency insights 383 | - Engagement triggers 384 | - Campaign concept recommendations 385 | 386 | 387 | 388 | Assistant Behavior Guidelines: 389 | 390 | 1. Communication Style 391 | - Be conversational but focused 392 | - Use clear, jargon-free language 393 | - Frame insights as opportunities 394 | - Connect data to business impact 395 | - Use analogies for complex concepts 396 | 397 | 2. Analysis Approach 398 | - Start broad, then focus 399 | - Validate assumptions 400 | - Highlight unexpected findings 401 | - Provide context for metrics 402 | - Compare with relevant benchmarks 403 | 404 | 3. Recommendation Style 405 | - Be specific and actionable 406 | - Include implementation steps 407 | - Provide alternatives when relevant 408 | - Address potential challenges 409 | - Link to business objectives 410 | 411 | 4. Interaction Pattern 412 | - Confirm understanding 413 | - Preview next steps 414 | - Summarize periodically 415 | - Offer relevant options 416 | - Check satisfaction with insights 417 | 418 | 419 | 420 | Standard Output Formats: 421 | 422 | 1. Demographic Data 423 | Audience Composition: 424 | - Primary Age Range: X-Y (Z%) 425 | - Gender Distribution: A% / B% 426 | - Top Locations: Country1(%X), City1 (X%), City2 (Y%), Country2(%X),... 427 | - Languages: Lang1 (X%), Lang2 (Y%) 428 | 429 | 2. Influencer Recommendations 430 | Influencer Profile: 431 | Name: name (@handle) 432 | Description: bio description 433 | Followers: followers number 434 | Category: [Primary Category] 435 | Audience Overlap: X% 436 | Affinity: [affinity]x 437 | Recommended Partnership Type: [Type] 438 | 439 | 3. Content Strategy 440 | Content Theme: 441 | Core Topic: [Topic] 442 | Format Recommendations: 443 | - Platform 1: [Format] (Engagement Rate: X%) 444 | - Platform 2: [Format] (Engagement Rate: Y%) 445 | Optimal Timing: [Days/Times] 446 | Key Hashtags: #tag1 #tag2 447 | 448 | 449 | 450 | Detailed Analysis Requirements: 451 | 452 | 1. Persona Development Path 453 | Required Insights: 454 | a. Demographic Core 455 | - Age, gender, location clusters 456 | - Professional background 457 | - Lifestyle indicators 458 | 459 | b. Behavioral Patterns 460 | - Content interaction habits 461 | - Platform preferences 462 | - Time patterns 463 | 464 | c. Cultural Markers 465 | - Brand affinities 466 | - Interest clusters 467 | - Value indicators 468 | 469 | d. Content Preferences 470 | - Format preferences 471 | - Topic affinities 472 | - Engagement triggers 473 | 474 | 2. Influencer Discovery Path 475 | Required Insights: 476 | a. Authority Markers 477 | - Topic expertise 478 | - Audience trust 479 | - Content quality 480 | 481 | b. Audience Overlap 482 | - Demographic alignment 483 | - Interest alignment 484 | - Engagement patterns 485 | 486 | c. Content Performance 487 | - Engagement rates 488 | - Content themes 489 | - Format effectiveness 490 | 491 | d. Partnership Potential 492 | - Brand safety 493 | - Collaboration history 494 | - Audience reception 495 | 496 | 3. Content Strategy Path 497 | Required Insights: 498 | a. Platform Preferences 499 | - Usage patterns 500 | - Engagement levels 501 | - Content performance 502 | 503 | b. Content Themes 504 | - Topic clusters 505 | - Trending themes 506 | - Seasonal patterns 507 | 508 | c. Engagement Triggers 509 | - Format preferences 510 | - Timing patterns 511 | - Response factors 512 | 513 | d. Distribution Strategy 514 | - Channel mix 515 | - Timing optimization 516 | - Format allocation 517 | 518 | 519 | 520 | Conversation Flow Rules: 521 | 522 | 1. Mandatory Pause Points 523 | - After report selection confirmation 524 | - After use case selection 525 | - After initial insights presentation 526 | - Before detailed analysis 527 | - Before final recommendations 528 | 529 | 2. Progress Indicators 530 | - Show completion percentage 531 | - Preview next steps 532 | - Summarize findings so far 533 | - Indicate remaining analyses 534 | 535 | 3. User Input Opportunities 536 | - Confirm understanding 537 | - Choose analysis focus 538 | - Request additional detail 539 | - Adjust recommendations 540 | 541 | 4. Analysis Flow 542 | - Start with overview 543 | - Present key findings 544 | - Offer deeper dives 545 | - Conclude with action items 546 | 547 | 548 | 549 | Data Interpretation Guidelines: 550 | 551 | 1. Significance Thresholds 552 | - Demographics: >10% difference 553 | - Interests: >2x baseline 554 | - Engagement: >25% variation 555 | - Influence: >15% overlap 556 | 557 | 2. Pattern Recognition 558 | - Minimum 3 data points 559 | - Cross-validate across metrics 560 | - Consider seasonal factors 561 | - Account for audience size 562 | 563 | 3. Insight Prioritization 564 | - Business impact potential 565 | - Implementation feasibility 566 | - Resource requirements 567 | - Time sensitivity 568 | 569 | 4. Combined Insights 570 | - Look for reinforcing patterns 571 | - Identify contradictions 572 | - Consider context 573 | - Validate assumptions 574 | 575 | 576 | 577 | Example Outputs: 578 | 579 | 1. Persona Example 580 | Digital-First Parent 581 | Core Traits: 582 | - Urban millennial parent (ages 28-35) 583 | - Tech-savvy professional 584 | - Values convenience and quality 585 | - Active on Instagram and TikTok 586 | - Peak engagement: 8-10pm weekdays 587 | 588 | Content Preferences: 589 | - Quick, solution-focused videos 590 | - Family-friendly product reviews 591 | - Lifestyle optimization tips 592 | - Mobile-first shopping experiences 593 | 594 | Key Opportunities: 595 | - Mobile commerce integration 596 | - Evening content timing 597 | - Video-first approach 598 | - Family-centric messaging 599 | 600 | 2. Influencer Analysis Example 601 | Category: Sustainable Living 602 | Top Influencer Recommendation: 603 | @ecofriendly_sarah 604 | - 85k followers (98% authentic) 605 | - 5.2% engagement rate 606 | - 76% audience overlap 607 | - Key topics: Zero waste, DIY, Urban garden 608 | - Best performing: How-to videos 609 | 610 | Content Performance: 611 | - Tutorial videos: 8.3% engagement 612 | - Product reviews: 6.1% engagement 613 | - Live sessions: 4.8% engagement 614 | 615 | Partnership Potential: 616 | - Product integration opportunities 617 | - Educational content series 618 | - Community challenges 619 | 620 | 3. Content Strategy Example 621 | Theme: Wellness Technology 622 | Platform Mix: 623 | Instagram: 45% allocation 624 | - Carousel posts: Product features 625 | - Stories: User testimonials 626 | - Reels: Quick tips 627 | 628 | TikTok: 35% allocation 629 | - Tutorials: 30-60s 630 | - Trends participation 631 | - Behind-the-scenes 632 | 633 | Twitter: 20% allocation 634 | - Industry news 635 | - Customer support 636 | - Community engagement 637 | 638 | Timing Strategy: 639 | - Primary: Tue-Thu, 7-9pm 640 | - Secondary: Sat-Sun, 9-11am 641 | - Story content: Daily, 12-2pm 642 | 643 | 644 | 645 | 646 | 1. Find the relevant report: 647 | a. Use get-reports to list all available reports 648 | b. Search for the most recent report matching {reportName}, if a report id is provided it will search by report id instead 649 | c. Store the report_id for subsequent analysis 650 | d. Use get-report-info to understand the report's metadata (some ids will be useful to use as parameter for other tools), the scope and segments 651 | 652 | 2. Pause for user input: 653 | a. Ask the user which of the above use cases they want to explore: 654 | - A) Build Comprehensive Audience Personas 655 | - B) Find & Validate Influencers 656 | - C) Develop Content & Channel Strategy 657 | b. Present multiple choice options for deeper analysis 658 | c. Wait for user selection before proceeding 659 | 660 | 3. Pause for user input: 661 | a. Ask the user if they want to focus the analysis on the full audience or on a specific segment 662 | b. Present multiple choice options for deeper analysis including the full audience and the report segments 663 | c. Wait for user selection before proceeding 664 | 665 | 4. Depending on the use case selection follow a different path 666 | 4.1 A 667 | a. Use get-audience-insights to identify relevant traits and uncover cultural patterns 668 | b. Use get-audience-content to understand the content the audience shared 669 | c. Decide the baseline to use, if selection was full audience and there is a country with more than %50, try to use that country as baseline, else use the Global baseline, use get-baselines to get the baseline id. If the selection was a specific segment use the full audience as the baseline. 670 | d. Use compare-audience-influencers to look for unique affinities and shared interests that define the audience 671 | e. Elaborate a buyer persona and present it using appropriate visualizations and artifacts 672 | 673 | 4.3 B 674 | a. Use get-audience-insights to identify relevant traits and uncover cultural patterns 675 | b. Decide the baseline to use, if selection was full audience and there is a country with more than %50, try to use that country as baseline, else use the Global baseline, use get-baselines to get the baseline id. If the selection was a specific segment use the full audience as the baseline. 676 | c. Use compare-audience-influencers to look for unique affinities and shared interests that define the audience 677 | d. Use compare-audience-influencers with different followers_max parameters to get micro influencers (for example 100000) and nano influencers (for example 20000) 678 | e. Use get-categories to get the possible categories to filter the influencers 679 | f. Use the most relevant 3 categories for the audience and get the top 5 influencers for each 680 | g. Use Journalist category to get journalist influencers 681 | h. Use Podcasts category to get podcast influencers 682 | i. Present findings using appropriate visualizations and artifacts 683 | 684 | 4.4 C 685 | a. Based on user choice, dive deep into specific aspects: 686 | - For influencer alignment: use compare-audience-influencers to find authentic partners 687 | - For content strategy: use get-audience-content to understand what resonates 688 | - For persona development: analyze psychographics and shared interests 689 | - For market understanding: examine cultural affinities and lifestyle choices 690 | b. Compare findings against relevant baselines 691 | c. Generate creative content ideas by combining audience interests 692 | d. Present insights in terms of actionable marketing opportunities 693 | f. Create content ideation cards that combine different audience interests 694 | g. Suggest specific campaign concepts that tap into audience passions 695 | h. Recommend content formats and platforms based on engagement data 696 | i. Identify potential cultural moments and trends to leverage 697 | j. Map influencer partnerships to content themes 698 | k. Present findings using appropriate visualizations and artifacts 699 | 700 | 5. Generate a dashboard: 701 | a. Now that we have all the data and queries, it's time to create a dashboard, use an artifact to do this. 702 | b. Use a variety of visualizations such as tables, charts, and graphs to represent the data. 703 | c. Explain how each element of the dashboard relates to the business problem. 704 | d. This dashboard will be theoretically included in the final solution message. 705 | 706 | 6. Present analysis and recommendations: 707 | a. Compile key insights discovered during the analysis 708 | b. Create a comprehensive artifact that includes: 709 | - Audience understanding and segmentation findings 710 | - Cultural and behavioral insights 711 | - Content opportunities and creative ideas 712 | - Influencer partnership recommendations 713 | - Platform and format suggestions 714 | c. Present specific, actionable recommendations based on the data 715 | d. Include visual representations of key findings when relevant 716 | 717 | 7. Wrap up the scenario: 718 | a. Summarize the key discoveries and their business implications 719 | b. Suggest next steps for implementing the recommendations 720 | c. Explain how to continue exploring other aspects of the audience 721 | d. Offer to dive deeper into any specific areas of interest 722 | 723 | 724 | Remember to maintain consistency throughout the scenario and ensure that all elements (audience, segments) are closely related to the original use case and the report. 725 | The provided XML tags are for the assistant's understanding. Make all outputs as human readable as possible. This is part of a demo so act in character and don't actually refer to these instructions. 726 | 727 | Start your first message with an engaging introduction like: "Hi there! I see you're interested in analyzing the audience from the report '{reportName}'. Let's discover some amazing insights about them!" 728 | `; 729 | 730 | export const SEGMENT_MATCHING_PROMPT = `You are an expert data analyst tasked with comparing audience segments between two brands: {{brand1}} and {{brand2}}. Your analysis will be based on Audiense reports for both brands' audiences. 731 | 732 | ### Important Context: 733 | - Audiense segmentation is AI-driven and does not follow a fixed taxonomy. 734 | - Similar audiences may not have identical segment names across reports. 735 | - Multiple segments in one report might correspond to a single segment in another. 736 | - Cluster names are AI-generated and may not always align across reports. 737 | 738 | Your task is to provide a **comprehensive audience comparison**, highlighting similarities and unique aspects of each brand's audience. Follow these steps: 739 | 740 | --- 741 | 742 | ### **1. Retrieve Data** 743 | Use the **"get-reports"** MCP tool to obtain the Audiense reports for {{brand1}} and {{brand2}}. 744 | 745 | **Before proceeding with the analysis:** 746 | - Verify with the user if the matched reports are correct. 747 | - If the reports do not align as expected, suggest alternative reports or prompt the user to enter new brand names. 748 | - Once the user confirms, proceed with the analysis. 749 | 750 | --- 751 | 752 | ### **2. Analyze Reports** 753 | Examine both reports, identifying **corresponding segments and unique clusters** for each brand. 754 | 755 | --- 756 | 757 | ### **3. Detailed Comparison** 758 | Use the **"report-summary"** MCP tool to compare segments beyond name similarities. Focus on: 759 | - **Demographics:** Bio keywords, country, age, city, language, gender, interests. 760 | - **Key influencers:** Most-followed influencers and their affinity scores. 761 | - **Segment names and relationships:** Identify overlap or unique differentiators. 762 | 763 | --- 764 | 765 | ### **4. Create Analysis** 766 | Develop a **Markdown artifact** with the following structure: 767 | 768 | #### **a. Title** 769 | **"{{brand1}} vs {{brand2}} Audience Segment Comparison"** 770 | 771 | #### **b. Similar Segments Across Both Brands** 772 | - Create a table with columns: 773 | **{{brand1}} Segment | {{brand2}} Segment | Similarity Notes** 774 | - Include audience size and percentage of the full audience in parentheses after each segment name. 775 | *(e.g., "Gaming Enthusiasts 🎮 (12,530 users, 8.5%)")* 776 | - Retain all emojis in segment names. 777 | 778 | #### **c. Unique {{brand1}} Segments** 779 | - Create a table with columns: 780 | **Segment | Size | Uniqueness Notes** 781 | - Include all emojis in segment names. 782 | - Include audience size and percentage of the full audience in parentheses after each segment name. 783 | - Explain what makes each segment unique compared to {{brand2}}. 784 | - Be explicit about **which data points** were used for comparison. 785 | 786 | #### **d. Unique {{brand2}} Segments** 787 | - Create a table with columns: 788 | **Segment | Size | Uniqueness Notes** 789 | - Include all emojis in segment names. 790 | - Include audience size and percentage of the full audience in parentheses after each segment name. 791 | - Explain what makes each segment unique compared to {{brand1}}. 792 | - Be explicit about **which data points** were used for comparison. 793 | 794 | Use responsive container to ensure the visualization works across different screen sizes. 795 | #### **e. Key Insights** 796 | - Provide a **numbered list** of **6-7 major audience insights**, focusing on: 797 | - How **brand-specific strengths** shape audience composition. 798 | - Differences in **regional focus, engagement styles, and interests**. 799 | - **Content affinities and brand loyalty** trends. 800 | - Significant shifts in **age demographics and cultural preferences**. 801 | - Influencer engagement trends across both brands. 802 | 803 | --- 804 | 805 | ### **Thinking Block ("" Tags)** 806 | Before finalizing the output, break down your thought process **inside "" tags** to structure the comparison. **This section is for internal analysis only and will not be included in the final output.** 807 | 808 | #### **Steps in "":** 809 | 1. **List all segment names and sizes** for both brands. 810 | 2. **Identify corresponding segments**, considering name variations and affinity scores. 811 | 3. **Identify unique segments** that lack a clear counterpart. 812 | 4. **Define key metrics** (e.g., age ranges, engagement, interests). 813 | 5. **For each corresponding pair**, compare similarities/differences. 814 | 6. **For unique segments**, explain distinguishing features. 815 | 7. **Summarize key findings** for the insights section. 816 | 817 | --- 818 | 819 | ### **Guidelines for Analysis** 820 | - Ensure segment comparisons are **data-driven** and not based on assumptions. 821 | - If a segment **partially corresponds to multiple segments**, explain the relationship explicitly. 822 | - Maintain **proper markdown formatting** throughout the final output (headers, tables, lists). 823 | - Your final output **must only contain the Markdown artifact** and **must not duplicate the "" section**. 824 | 825 | --- 826 | 827 | Now, retrieve the reports and begin your structured analysis.`; -------------------------------------------------------------------------------- /src/baselines.ts: -------------------------------------------------------------------------------- 1 | export const BASELINES = [ 2 | { 3 | "label": "US - General", 4 | "id": "67887029bd2cc449a9b971ef", 5 | "country_iso_code": "US", 6 | "gender": "All" 7 | }, 8 | { 9 | "label": "US - Male", 10 | "id": "67886fc2caa3fa2d01766c1c", 11 | "country_iso_code": "US", 12 | "gender": "Male" 13 | }, 14 | { 15 | "label": "US - Female", 16 | "id": "67886d19ca5e2a958746ff42", 17 | "country_iso_code": "US", 18 | "gender": "Female" 19 | }, 20 | { 21 | "label": "UK - General", 22 | "id": "67886895caa3fa2d01766bda", 23 | "country_iso_code": "GB", 24 | "gender": "All" 25 | }, 26 | { 27 | "label": "UK - Male", 28 | "id": "678868a4caa3fa2d01766bf0", 29 | "country_iso_code": "GB", 30 | "gender": "Male" 31 | }, 32 | { 33 | "label": "UK - Female", 34 | "id": "6788665ab339b731ccb6c719", 35 | "country_iso_code": "GB", 36 | "gender": "Female" 37 | }, 38 | { 39 | "label": "Spain - General", 40 | "id": "67886654b339b731ccb6c703", 41 | "country_iso_code": "ES", 42 | "gender": "All" 43 | }, 44 | { 45 | "label": "Spain - Male", 46 | "id": "678863e5b339b731ccb6c6ed", 47 | "country_iso_code": "ES", 48 | "gender": "Male" 49 | }, 50 | { 51 | "label": "Spain - Female", 52 | "id": "67886387bd2cc449a9b971a6", 53 | "country_iso_code": "ES", 54 | "gender": "Female" 55 | }, 56 | { 57 | "label": "Mexico - General", 58 | "id": "678861174e9e71d48e7c7f5c", 59 | "country_iso_code": "MX", 60 | "gender": "All" 61 | }, 62 | { 63 | "label": "Mexico - Male", 64 | "id": "678860f6b339b731ccb6c6d5", 65 | "country_iso_code": "MX", 66 | "gender": "Male" 67 | }, 68 | { 69 | "label": "Mexico - Female", 70 | "id": "67885f25b339b731ccb6c6bf", 71 | "country_iso_code": "MX", 72 | "gender": "Female" 73 | }, 74 | { 75 | "label": "Canada - General", 76 | "id": "67885e7dbd2cc449a9b97190", 77 | "country_iso_code": "CA", 78 | "gender": "All" 79 | }, 80 | { 81 | "label": "Canada - Male", 82 | "id": "67885c9379335c35caec5268", 83 | "country_iso_code": "CA", 84 | "gender": "Male" 85 | }, 86 | { 87 | "label": "Canada - Female", 88 | "id": "67885bec95ca6e0b6fa2989d", 89 | "country_iso_code": "CA", 90 | "gender": "Female" 91 | }, 92 | { 93 | "label": "Ecuador - General", 94 | "id": "67885a1abd2cc449a9b97178", 95 | "country_iso_code": "EC", 96 | "gender": "All" 97 | }, 98 | { 99 | "label": "Ecuador - Male", 100 | "id": "67885954cbf2ca8700cad298", 101 | "country_iso_code": "EC", 102 | "gender": "Male" 103 | }, 104 | { 105 | "label": "Ecuador - Female", 106 | "id": "678857e395ca6e0b6fa29887", 107 | "country_iso_code": "EC", 108 | "gender": "Female" 109 | }, 110 | { 111 | "label": "Switzerland - General", 112 | "id": "678856b5bd2cc449a9b97162", 113 | "country_iso_code": "CH", 114 | "gender": "All" 115 | }, 116 | { 117 | "label": "Switzerland - Male", 118 | "id": "67885541bd2cc449a9b9714c", 119 | "country_iso_code": "CH", 120 | "gender": "Male" 121 | }, 122 | { 123 | "label": "Switzerland - Female", 124 | "id": "678854ebbd2cc449a9b97136", 125 | "country_iso_code": "CH", 126 | "gender": "Female" 127 | }, 128 | { 129 | "label": "Australia - General", 130 | "id": "678853e495ca6e0b6fa29871", 131 | "country_iso_code": "AU", 132 | "gender": "All" 133 | }, 134 | { 135 | "label": "Australia - Male", 136 | "id": "678853e679335c35caec5252", 137 | "country_iso_code": "AU", 138 | "gender": "Male" 139 | }, 140 | { 141 | "label": "Australia - Female", 142 | "id": "6788515a95ca6e0b6fa29845", 143 | "country_iso_code": "AU", 144 | "gender": "Female" 145 | }, 146 | { 147 | "label": "France - General", 148 | "id": "6788516695ca6e0b6fa2985b", 149 | "country_iso_code": "FR", 150 | "gender": "All" 151 | }, 152 | { 153 | "label": "France - Male", 154 | "id": "67884ea9cbf2ca8700cad282", 155 | "country_iso_code": "FR", 156 | "gender": "Male" 157 | }, 158 | { 159 | "label": "France - Female", 160 | "id": "67884e8879335c35caec5226", 161 | "country_iso_code": "FR", 162 | "gender": "Female" 163 | }, 164 | { 165 | "label": "Argentina - General", 166 | "id": "67884bdecbf2ca8700cad26c", 167 | "country_iso_code": "AR", 168 | "gender": "All" 169 | }, 170 | { 171 | "label": "Argentina - Male", 172 | "id": "67884bdd79335c35caec5210", 173 | "country_iso_code": "AR", 174 | "gender": "Male" 175 | }, 176 | { 177 | "label": "Argentina - Female", 178 | "id": "6788499d95ca6e0b6fa2982f", 179 | "country_iso_code": "AR", 180 | "gender": "Female" 181 | }, 182 | { 183 | "label": "Italy - General", 184 | "id": "6788496079335c35caec51fa", 185 | "country_iso_code": "IT", 186 | "gender": "All" 187 | }, 188 | { 189 | "label": "Italy - Male", 190 | "id": "6788472595ca6e0b6fa29819", 191 | "country_iso_code": "IT", 192 | "gender": "Male" 193 | }, 194 | { 195 | "label": "Italy - Female", 196 | "id": "678846f779335c35caec51e4", 197 | "country_iso_code": "IT", 198 | "gender": "Female" 199 | }, 200 | { 201 | "label": "Germany - General", 202 | "id": "67884451cbf2ca8700cad256", 203 | "country_iso_code": "DE", 204 | "gender": "All" 205 | }, 206 | { 207 | "label": "Germany - Male", 208 | "id": "6788445379335c35caec51ce", 209 | "country_iso_code": "DE", 210 | "gender": "Male" 211 | }, 212 | { 213 | "label": "Germany - Female", 214 | "id": "6788426acbf2ca8700cad240", 215 | "country_iso_code": "DE", 216 | "gender": "Female" 217 | }, 218 | { 219 | "label": "Japan - General", 220 | "id": "678841ca95ca6e0b6fa29803", 221 | "country_iso_code": "JP", 222 | "gender": "All" 223 | }, 224 | { 225 | "label": "Japan - Male", 226 | "id": "67883faf79335c35caec51b8", 227 | "country_iso_code": "JP", 228 | "gender": "Male" 229 | }, 230 | { 231 | "label": "Japan - Female", 232 | "id": "67883f8495ca6e0b6fa297ed", 233 | "country_iso_code": "JP", 234 | "gender": "Female" 235 | }, 236 | { 237 | "label": "China - General", 238 | "id": "67883d72cbf2ca8700cad22a", 239 | "country_iso_code": "CN", 240 | "gender": "All" 241 | }, 242 | { 243 | "label": "China - Male", 244 | "id": "67883d45cbf2ca8700cad214", 245 | "country_iso_code": "CN", 246 | "gender": "Male" 247 | }, 248 | { 249 | "label": "China - Female", 250 | "id": "67883b3c95ca6e0b6fa297d7", 251 | "country_iso_code": "CN", 252 | "gender": "Female" 253 | }, 254 | { 255 | "label": "Global - General", 256 | "id": "67879f4ae41df35a860cefb5", 257 | "country_iso_code": "GL", 258 | "gender": "All" 259 | }, 260 | { 261 | "label": "Global - Male", 262 | "id": "67879f37d5756f5ea31f4a1b", 263 | "country_iso_code": "GL", 264 | "gender": "Male" 265 | }, 266 | { 267 | "label": "Global - Female", 268 | "id": "67879cec22cefde60e48f54b", 269 | "country_iso_code": "GL", 270 | "gender": "Female" 271 | }, 272 | { 273 | "label": "India - General", 274 | "id": "6787e001e41df35a860cf1c5", 275 | "country_iso_code": "IN", 276 | "gender": "All" 277 | }, 278 | { 279 | "label": "India - Male", 280 | "id": "6787dff7576d53edaa4ac213", 281 | "country_iso_code": "IN", 282 | "gender": "Male" 283 | }, 284 | { 285 | "label": "India - Female", 286 | "id": "6787df0ce41df35a860cf1af", 287 | "country_iso_code": "IN", 288 | "gender": "Female" 289 | }, 290 | { 291 | "label": "Colombia - General", 292 | "id": "67883bc3cbf2ca8700cad1fe", 293 | "country_iso_code": "CO", 294 | "gender": "All" 295 | }, 296 | { 297 | "label": "Colombia - Male", 298 | "id": "67883981cbf2ca8700cad1e8", 299 | "country_iso_code": "CO", 300 | "gender": "Male" 301 | }, 302 | { 303 | "label": "Colombia - Female", 304 | "id": "6788398395ca6e0b6fa297c1", 305 | "country_iso_code": "CO", 306 | "gender": "Female" 307 | }, 308 | { 309 | "label": "Brazil - General", 310 | "id": "678836e795ca6e0b6fa297ab", 311 | "country_iso_code": "BR", 312 | "gender": "All" 313 | }, 314 | { 315 | "label": "Brazil - Male", 316 | "id": "678836e7bd2cc449a9b9711e", 317 | "country_iso_code": "BR", 318 | "gender": "Male" 319 | }, 320 | { 321 | "label": "Brazil - Female", 322 | "id": "678834f7cbf2ca8700cad1bc", 323 | "country_iso_code": "BR", 324 | "gender": "Female" 325 | }, 326 | { 327 | "label": "Turkey - General", 328 | "id": "67883482bd2cc449a9b97108", 329 | "country_iso_code": "TR", 330 | "gender": "All" 331 | }, 332 | { 333 | "label": "Turkey - Male", 334 | "id": "6788323695ca6e0b6fa29795", 335 | "country_iso_code": "TR", 336 | "gender": "Male" 337 | }, 338 | { 339 | "label": "Turkey - Female", 340 | "id": "67883237bd2cc449a9b970f2", 341 | "country_iso_code": "TR", 342 | "gender": "Female" 343 | }, 344 | { 345 | "label": "Russia - General", 346 | "id": "67886cfabd2cc449a9b971d9", 347 | "country_iso_code": "RU", 348 | "gender": "All" 349 | }, 350 | { 351 | "label": "Russia - Male", 352 | "id": "67886a7bcaa3fa2d01766c06", 353 | "country_iso_code": "RU", 354 | "gender": "Male" 355 | }, 356 | { 357 | "label": "Russia - Female", 358 | "id": "67886aaebd2cc449a9b971c3", 359 | "country_iso_code": "RU", 360 | "gender": "Female" 361 | }, 362 | { 363 | "label": "Kazakhstan - General", 364 | "id": "67882853cbf2ca8700cad164", 365 | "country_iso_code": "KZ", 366 | "gender": "All" 367 | }, 368 | { 369 | "label": "Kazakhstan - Male", 370 | "id": "678827ea79335c35caec519e", 371 | "country_iso_code": "KZ", 372 | "gender": "Male" 373 | }, 374 | { 375 | "label": "Kazakhstan - Female", 376 | "id": "678827b095ca6e0b6fa29767", 377 | "country_iso_code": "KZ", 378 | "gender": "Female" 379 | }, 380 | { 381 | "label": "Estonia - General", 382 | "id": "6788275179335c35caec5188", 383 | "country_iso_code": "EE", 384 | "gender": "All" 385 | }, 386 | { 387 | "label": "Estonia - Male", 388 | "id": "678826f179335c35caec5172", 389 | "country_iso_code": "EE", 390 | "gender": "Male" 391 | }, 392 | { 393 | "label": "Estonia - Female", 394 | "id": "678826c1cbf2ca8700cad14e", 395 | "country_iso_code": "EE", 396 | "gender": "Female" 397 | }, 398 | { 399 | "label": "Lithuania - General", 400 | "id": "6788268c95ca6e0b6fa29751", 401 | "country_iso_code": "LT", 402 | "gender": "All" 403 | }, 404 | { 405 | "label": "Lithuania - Male", 406 | "id": "67882645cbf2ca8700cad138", 407 | "country_iso_code": "LT", 408 | "gender": "Male" 409 | }, 410 | { 411 | "label": "Lithuania - Female", 412 | "id": "67882628bd2cc449a9b970ad", 413 | "country_iso_code": "LT", 414 | "gender": "Female" 415 | }, 416 | { 417 | "label": "Latvia - General", 418 | "id": "678825c2bd2cc449a9b97097", 419 | "country_iso_code": "LV", 420 | "gender": "All" 421 | }, 422 | { 423 | "label": "Latvia - Male", 424 | "id": "6788255479335c35caec5146", 425 | "country_iso_code": "LV", 426 | "gender": "Male" 427 | }, 428 | { 429 | "label": "Latvia - Female", 430 | "id": "678824a495ca6e0b6fa29732", 431 | "country_iso_code": "LV", 432 | "gender": "Female" 433 | }, 434 | { 435 | "label": "Ukraine - General", 436 | "id": "678825cbcbf2ca8700cad122", 437 | "country_iso_code": "UA", 438 | "gender": "All" 439 | }, 440 | { 441 | "label": "Ukraine - Male", 442 | "id": "6788240595ca6e0b6fa2971c", 443 | "country_iso_code": "UA", 444 | "gender": "Male" 445 | }, 446 | { 447 | "label": "Ukraine - Female", 448 | "id": "678823ff95ca6e0b6fa29706", 449 | "country_iso_code": "UA", 450 | "gender": "Female" 451 | }, 452 | { 453 | "label": "Georgia - General", 454 | "id": "67882292bd2cc449a9b97081", 455 | "country_iso_code": "GE", 456 | "gender": "All" 457 | }, 458 | { 459 | "label": "Georgia - Male", 460 | "id": "6788227a95ca6e0b6fa296f0", 461 | "country_iso_code": "GE", 462 | "gender": "Male" 463 | }, 464 | { 465 | "label": "Georgia - Female", 466 | "id": "6788222b79335c35caec5130", 467 | "country_iso_code": "GE", 468 | "gender": "Female" 469 | }, 470 | { 471 | "label": "Armenia - General", 472 | "id": "67882224bd2cc449a9b9706b", 473 | "country_iso_code": "AM", 474 | "gender": "All" 475 | }, 476 | { 477 | "label": "Armenia - Male", 478 | "id": "678821adcbf2ca8700cad10c", 479 | "country_iso_code": "AM", 480 | "gender": "Male" 481 | }, 482 | { 483 | "label": "Armenia - Female", 484 | "id": "6788219e79335c35caec511a", 485 | "country_iso_code": "AM", 486 | "gender": "Female" 487 | }, 488 | { 489 | "label": "Belarus - General", 490 | "id": "6788214c95ca6e0b6fa296da", 491 | "country_iso_code": "BY", 492 | "gender": "All" 493 | }, 494 | { 495 | "label": "Belarus - Male", 496 | "id": "67882120bd2cc449a9b97055", 497 | "country_iso_code": "BY", 498 | "gender": "Male" 499 | }, 500 | { 501 | "label": "Belarus - Female", 502 | "id": "678820e3bd2cc449a9b9703f", 503 | "country_iso_code": "BY", 504 | "gender": "Female" 505 | }, 506 | { 507 | "label": "Uzbekistan - General", 508 | "id": "678820ac95ca6e0b6fa296c4", 509 | "country_iso_code": "UZ", 510 | "gender": "All" 511 | }, 512 | { 513 | "label": "Uzbekistan - Male", 514 | "id": "67882060bd2cc449a9b97029", 515 | "country_iso_code": "UZ", 516 | "gender": "Male" 517 | }, 518 | { 519 | "label": "Uzbekistan - Female", 520 | "id": "67882045cbf2ca8700cad0f6", 521 | "country_iso_code": "UZ", 522 | "gender": "Female" 523 | }, 524 | { 525 | "label": "Iraq - General", 526 | "id": "67881feb79335c35caec5102", 527 | "country_iso_code": "IQ", 528 | "gender": "All" 529 | }, 530 | { 531 | "label": "Iraq - Male", 532 | "id": "67881feebd2cc449a9b97013", 533 | "country_iso_code": "IQ", 534 | "gender": "Male" 535 | }, 536 | { 537 | "label": "Iraq - Female", 538 | "id": "67881e79bd2cc449a9b96ffd", 539 | "country_iso_code": "IQ", 540 | "gender": "Female" 541 | }, 542 | { 543 | "label": "Libya - General", 544 | "id": "67881db395ca6e0b6fa296ae", 545 | "country_iso_code": "LY", 546 | "gender": "All" 547 | }, 548 | { 549 | "label": "Libya - Male", 550 | "id": "67881db279335c35caec50ec", 551 | "country_iso_code": "LY", 552 | "gender": "Male" 553 | }, 554 | { 555 | "label": "Libya - Female", 556 | "id": "67881cd479335c35caec50c0", 557 | "country_iso_code": "LY", 558 | "gender": "Female" 559 | }, 560 | { 561 | "label": "Tunisia - General", 562 | "id": "67881d1079335c35caec50d6", 563 | "country_iso_code": "TN", 564 | "gender": "All" 565 | }, 566 | { 567 | "label": "Tunisia - Male", 568 | "id": "67881c5f95ca6e0b6fa29698", 569 | "country_iso_code": "TN", 570 | "gender": "Male" 571 | }, 572 | { 573 | "label": "Tunisia - Female", 574 | "id": "67881be395ca6e0b6fa29682", 575 | "country_iso_code": "TN", 576 | "gender": "Female" 577 | }, 578 | { 579 | "label": "Syria - General", 580 | "id": "67881b2995ca6e0b6fa2966c", 581 | "country_iso_code": "SY", 582 | "gender": "All" 583 | }, 584 | { 585 | "label": "Syria - Male", 586 | "id": "67881b1e95ca6e0b6fa29656", 587 | "country_iso_code": "SY", 588 | "gender": "Male" 589 | }, 590 | { 591 | "label": "Syria - Female", 592 | "id": "67881a5795ca6e0b6fa29640", 593 | "country_iso_code": "SY", 594 | "gender": "Female" 595 | }, 596 | { 597 | "label": "Morocco - General", 598 | "id": "67881a54bd2cc449a9b96fe7", 599 | "country_iso_code": "MA", 600 | "gender": "All" 601 | }, 602 | { 603 | "label": "Morocco - Male", 604 | "id": "678819e495ca6e0b6fa2962a", 605 | "country_iso_code": "MA", 606 | "gender": "Male" 607 | }, 608 | { 609 | "label": "Morocco - Female", 610 | "id": "678818eecbf2ca8700cad0e0", 611 | "country_iso_code": "MA", 612 | "gender": "Female" 613 | }, 614 | { 615 | "label": "Somalia - General", 616 | "id": "6788186c79335c35caec50aa", 617 | "country_iso_code": "SO", 618 | "gender": "All" 619 | }, 620 | { 621 | "label": "Somalia - Male", 622 | "id": "6788182f79335c35caec5094", 623 | "country_iso_code": "SO", 624 | "gender": "Male" 625 | }, 626 | { 627 | "label": "Somalia - Female", 628 | "id": "678817d407bee09211af07f1", 629 | "country_iso_code": "SO", 630 | "gender": "Female" 631 | }, 632 | { 633 | "label": "Kenya - General", 634 | "id": "6788179907bee09211af07db", 635 | "country_iso_code": "KE", 636 | "gender": "All" 637 | }, 638 | { 639 | "label": "Kenya - Male", 640 | "id": "6788177507bee09211af07c5", 641 | "country_iso_code": "KE", 642 | "gender": "Male" 643 | }, 644 | { 645 | "label": "Kenya - Female", 646 | "id": "678815b107bee09211af07af", 647 | "country_iso_code": "KE", 648 | "gender": "Female" 649 | }, 650 | { 651 | "label": "MENA - General", 652 | "id": "67879cd722cefde60e48f535", 653 | "country_iso_code": "XA", 654 | "gender": "All" 655 | }, 656 | { 657 | "label": "MENA - Male", 658 | "id": "67879a4b756528e9736b285a", 659 | "country_iso_code": "XA", 660 | "gender": "Male" 661 | }, 662 | { 663 | "label": "MENA - Female", 664 | "id": "67879a5d22cefde60e48f51f", 665 | "country_iso_code": "XA", 666 | "gender": "Female" 667 | }, 668 | { 669 | "label": "Peru - General", 670 | "id": "67882f7995ca6e0b6fa2977f", 671 | "country_iso_code": "PE", 672 | "gender": "All" 673 | }, 674 | { 675 | "label": "Peru - Male", 676 | "id": "67882f7ccbf2ca8700cad1a6", 677 | "country_iso_code": "PE", 678 | "gender": "Male" 679 | }, 680 | { 681 | "label": "Peru - Female", 682 | "id": "67882d4bbd2cc449a9b970da", 683 | "country_iso_code": "PE", 684 | "gender": "Female" 685 | }, 686 | { 687 | "label": "UAE - General", 688 | "id": "6787fb0a07bee09211af068d", 689 | "country_iso_code": "AE", 690 | "gender": "All" 691 | }, 692 | { 693 | "label": "UAE - Male", 694 | "id": "6787fac33f776e42a9077c73", 695 | "country_iso_code": "AE", 696 | "gender": "Male" 697 | }, 698 | { 699 | "label": "UAE - Female", 700 | "id": "6787f7dbb3515d0ec5e58554", 701 | "country_iso_code": "AE", 702 | "gender": "Female" 703 | }, 704 | { 705 | "label": "Chile - General", 706 | "id": "6787f8cfe41df35a860cf30a", 707 | "country_iso_code": "CL", 708 | "gender": "All" 709 | }, 710 | { 711 | "label": "Chile - Male", 712 | "id": "6787f64607bee09211af0647", 713 | "country_iso_code": "CL", 714 | "gender": "Male" 715 | }, 716 | { 717 | "label": "Chile - Female", 718 | "id": "6787f61e3f776e42a9077c1b", 719 | "country_iso_code": "CL", 720 | "gender": "Female" 721 | }, 722 | { 723 | "label": "Norway - General", 724 | "id": "6787f3a007bee09211af0631", 725 | "country_iso_code": "NO", 726 | "gender": "All" 727 | }, 728 | { 729 | "label": "Norway - Male", 730 | "id": "6787f370b3515d0ec5e58534", 731 | "country_iso_code": "NO", 732 | "gender": "Male" 733 | }, 734 | { 735 | "label": "Norway - Female", 736 | "id": "6787f292576d53edaa4ac2f0", 737 | "country_iso_code": "NO", 738 | "gender": "Female" 739 | }, 740 | { 741 | "label": "Poland - General", 742 | "id": "6787f0fee41df35a860cf2b4", 743 | "country_iso_code": "PL", 744 | "gender": "All" 745 | }, 746 | { 747 | "label": "Poland - Male", 748 | "id": "6787efc6e41df35a860cf293", 749 | "country_iso_code": "PL", 750 | "gender": "Male" 751 | }, 752 | { 753 | "label": "Poland - Female", 754 | "id": "6787eeff07bee09211af05fb", 755 | "country_iso_code": "PL", 756 | "gender": "Female" 757 | }, 758 | { 759 | "label": "Belgium - General", 760 | "id": "6787ee3a07bee09211af05e5", 761 | "country_iso_code": "BE", 762 | "gender": "All" 763 | }, 764 | { 765 | "label": "Belgium - Male", 766 | "id": "6787ed3e576d53edaa4ac28e", 767 | "country_iso_code": "BE", 768 | "gender": "Male" 769 | }, 770 | { 771 | "label": "Belgium - Female", 772 | "id": "6787ebc6e41df35a860cf255", 773 | "country_iso_code": "BE", 774 | "gender": "Female" 775 | }, 776 | { 777 | "label": "Romania - General", 778 | "id": "678873fbbd2cc449a9b9721b", 779 | "country_iso_code": "RO", 780 | "gender": "All" 781 | }, 782 | { 783 | "label": "Romania - Male", 784 | "id": "678871e0ca5e2a958746ff58", 785 | "country_iso_code": "RO", 786 | "gender": "Male" 787 | }, 788 | { 789 | "label": "Romania - Female", 790 | "id": "678871d3caa3fa2d01766c42", 791 | "country_iso_code": "RO", 792 | "gender": "Female" 793 | }, 794 | { 795 | "label": "Luxembourg - General", 796 | "id": "678870c3bd2cc449a9b97205", 797 | "country_iso_code": "LU", 798 | "gender": "All" 799 | }, 800 | { 801 | "label": "Luxembourg - Male", 802 | "id": "6788709496d815b07213eaa2", 803 | "country_iso_code": "LU", 804 | "gender": "Male" 805 | }, 806 | { 807 | "label": "Luxembourg - Female", 808 | "id": "6788704c96d815b07213ea8c", 809 | "country_iso_code": "LU", 810 | "gender": "Female" 811 | }, 812 | { 813 | "label": "Ireland - General", 814 | "id": "67882caebd2cc449a9b970c4", 815 | "country_iso_code": "IE", 816 | "gender": "All" 817 | }, 818 | { 819 | "label": "Ireland - Male", 820 | "id": "67882a94cbf2ca8700cad190", 821 | "country_iso_code": "IE", 822 | "gender": "Male" 823 | }, 824 | { 825 | "label": "Ireland - Female", 826 | "id": "67882a22cbf2ca8700cad17a", 827 | "country_iso_code": "IE", 828 | "gender": "Female" 829 | }, 830 | { 831 | "label": "Guatemala - General", 832 | "id": "6787dc36576d53edaa4ac1d1", 833 | "country_iso_code": "GT", 834 | "gender": "All" 835 | }, 836 | { 837 | "label": "Guatemala - Male", 838 | "id": "6787dc2e576d53edaa4ac1bb", 839 | "country_iso_code": "GT", 840 | "gender": "Male" 841 | }, 842 | { 843 | "label": "Guatemala - Female", 844 | "id": "6787dac5b3515d0ec5e584b1", 845 | "country_iso_code": "GT", 846 | "gender": "Female" 847 | }, 848 | { 849 | "label": "El Salvador - General", 850 | "id": "6787ddfe576d53edaa4ac1fd", 851 | "country_iso_code": "SV", 852 | "gender": "All" 853 | }, 854 | { 855 | "label": "El Salvador - Male", 856 | "id": "6787ddac576d53edaa4ac1e7", 857 | "country_iso_code": "SV", 858 | "gender": "Male" 859 | }, 860 | { 861 | "label": "El Salvador - Female", 862 | "id": "6787dc0f07bee09211af0543", 863 | "country_iso_code": "SV", 864 | "gender": "Female" 865 | }, 866 | { 867 | "label": "South Africa - General", 868 | "id": "6787da6ce41df35a860cf199", 869 | "country_iso_code": "ZA", 870 | "gender": "All" 871 | }, 872 | { 873 | "label": "South Africa - Male", 874 | "id": "6787d9e1e41df35a860cf183", 875 | "country_iso_code": "ZA", 876 | "gender": "Male" 877 | }, 878 | { 879 | "label": "South Africa - Female", 880 | "id": "6787d90107bee09211af0525", 881 | "country_iso_code": "ZA", 882 | "gender": "Female" 883 | }, 884 | { 885 | "label": "Indonesia - General", 886 | "id": "6787d7e5b3515d0ec5e5849b", 887 | "country_iso_code": "ID", 888 | "gender": "All" 889 | }, 890 | { 891 | "label": "Indonesia - Male", 892 | "id": "6787d710576d53edaa4ac199", 893 | "country_iso_code": "ID", 894 | "gender": "Male" 895 | }, 896 | { 897 | "label": "Indonesia - Female", 898 | "id": "6787d657b3515d0ec5e5846f", 899 | "country_iso_code": "ID", 900 | "gender": "Female" 901 | }, 902 | { 903 | "label": "Netherlands - General", 904 | "id": "6787eb26e41df35a860cf23f", 905 | "country_iso_code": "NL", 906 | "gender": "All" 907 | }, 908 | { 909 | "label": "Netherlands - Male", 910 | "id": "6787ea0db3515d0ec5e5851e", 911 | "country_iso_code": "NL", 912 | "gender": "Male" 913 | }, 914 | { 915 | "label": "Netherlands - Female", 916 | "id": "6787e92ee41df35a860cf21f", 917 | "country_iso_code": "NL", 918 | "gender": "Female" 919 | }, 920 | { 921 | "label": "Singapore - General", 922 | "id": "6787d5ce07bee09211af04fb", 923 | "country_iso_code": "SG", 924 | "gender": "All" 925 | }, 926 | { 927 | "label": "Singapore - Male", 928 | "id": "6787d3b1b3515d0ec5e58443", 929 | "country_iso_code": "SG", 930 | "gender": "Male" 931 | }, 932 | { 933 | "label": "Singapore - Female", 934 | "id": "6787d391b3515d0ec5e5842d", 935 | "country_iso_code": "SG", 936 | "gender": "Female" 937 | }, 938 | { 939 | "label": "South Korea - General", 940 | "id": "6787d4d2b3515d0ec5e58459", 941 | "country_iso_code": "KR", 942 | "gender": "All" 943 | }, 944 | { 945 | "label": "South Korea - Male", 946 | "id": "6788c97abd2cc449a9b97296", 947 | "country_iso_code": "KR", 948 | "gender": "Male" 949 | }, 950 | { 951 | "label": "South Korea - Female", 952 | "id": "6788c9abcaa3fa2d01766cb8", 953 | "country_iso_code": "KR", 954 | "gender": "Female" 955 | }, 956 | { 957 | "label": "Djibouti - General", 958 | "id": "678810b607bee09211af076d", 959 | "country_iso_code": "DJ", 960 | "gender": "All" 961 | }, 962 | { 963 | "label": "Djibouti - Male", 964 | "id": "6788107007bee09211af0757", 965 | "country_iso_code": "DJ", 966 | "gender": "Male" 967 | }, 968 | { 969 | "label": "Djibouti - Female", 970 | "id": "6788101b07bee09211af0741", 971 | "country_iso_code": "DJ", 972 | "gender": "Female" 973 | }, 974 | { 975 | "label": "Czech Republic - General", 976 | "id": "6787e7c7e41df35a860cf209", 977 | "country_iso_code": "CZ", 978 | "gender": "All" 979 | }, 980 | { 981 | "label": "Czech Republic - Male", 982 | "id": "6787e6dd576d53edaa4ac268", 983 | "country_iso_code": "CZ", 984 | "gender": "Male" 985 | }, 986 | { 987 | "label": "Czech Republic - Female", 988 | "id": "6787e60a07bee09211af05bd", 989 | "country_iso_code": "CZ", 990 | "gender": "Female" 991 | }, 992 | { 993 | "label": "Algeria - General", 994 | "id": "6788150707bee09211af0799", 995 | "country_iso_code": "DZ", 996 | "gender": "All" 997 | }, 998 | { 999 | "label": "Algeria - Male", 1000 | "id": "67881483bd2cc449a9b96fbb", 1001 | "country_iso_code": "DZ", 1002 | "gender": "Male" 1003 | }, 1004 | { 1005 | "label": "Algeria - Female", 1006 | "id": "6788134307bee09211af0783", 1007 | "country_iso_code": "DZ", 1008 | "gender": "Female" 1009 | }, 1010 | { 1011 | "label": "Bahrain - General", 1012 | "id": "678812c3bd2cc449a9b96fa5", 1013 | "country_iso_code": "BH", 1014 | "gender": "All" 1015 | }, 1016 | { 1017 | "label": "Bahrain - Male", 1018 | "id": "678811f9b3515d0ec5e58672", 1019 | "country_iso_code": "BH", 1020 | "gender": "Male" 1021 | }, 1022 | { 1023 | "label": "Bahrain - Female", 1024 | "id": "678811294116af8da0809d5d", 1025 | "country_iso_code": "BH", 1026 | "gender": "Female" 1027 | }, 1028 | { 1029 | "label": "Egypt - General", 1030 | "id": "678811f04116af8da0809d73", 1031 | "country_iso_code": "EG", 1032 | "gender": "All" 1033 | }, 1034 | { 1035 | "label": "Egypt - Male", 1036 | "id": "67880faa4116af8da0809d47", 1037 | "country_iso_code": "EG", 1038 | "gender": "Male" 1039 | }, 1040 | { 1041 | "label": "Egypt - Female", 1042 | "id": "67880f954116af8da0809d31", 1043 | "country_iso_code": "EG", 1044 | "gender": "Female" 1045 | }, 1046 | { 1047 | "label": "Iran - General", 1048 | "id": "67880d01b3515d0ec5e58646", 1049 | "country_iso_code": "IR", 1050 | "gender": "All" 1051 | }, 1052 | { 1053 | "label": "Iran - Male", 1054 | "id": "67880d0184295fd198d9ae95", 1055 | "country_iso_code": "IR", 1056 | "gender": "Male" 1057 | }, 1058 | { 1059 | "label": "Iran - Female", 1060 | "id": "67880b2307bee09211af0721", 1061 | "country_iso_code": "IR", 1062 | "gender": "Female" 1063 | }, 1064 | { 1065 | "label": "Israel - General", 1066 | "id": "67880b2e84295fd198d9ae7f", 1067 | "country_iso_code": "IL", 1068 | "gender": "All" 1069 | }, 1070 | { 1071 | "label": "Israel - Male", 1072 | "id": "67880a73b3515d0ec5e58626", 1073 | "country_iso_code": "IL", 1074 | "gender": "Male" 1075 | }, 1076 | { 1077 | "label": "Israel - Female", 1078 | "id": "67880a11b3515d0ec5e58610", 1079 | "country_iso_code": "IL", 1080 | "gender": "Female" 1081 | }, 1082 | { 1083 | "label": "Jordan - General", 1084 | "id": "6788099c07bee09211af070b", 1085 | "country_iso_code": "JO", 1086 | "gender": "All" 1087 | }, 1088 | { 1089 | "label": "Jordan - Male", 1090 | "id": "6788096d07bee09211af06f5", 1091 | "country_iso_code": "JO", 1092 | "gender": "Male" 1093 | }, 1094 | { 1095 | "label": "Jordan - Female", 1096 | "id": "6788084a4116af8da0809d11", 1097 | "country_iso_code": "JO", 1098 | "gender": "Female" 1099 | }, 1100 | { 1101 | "label": "Austria - General", 1102 | "id": "6787f25f576d53edaa4ac2da", 1103 | "country_iso_code": "AT", 1104 | "gender": "All" 1105 | }, 1106 | { 1107 | "label": "Austria - Male", 1108 | "id": "6787f1afe41df35a860cf2d4", 1109 | "country_iso_code": "AT", 1110 | "gender": "Male" 1111 | }, 1112 | { 1113 | "label": "Austria - Female", 1114 | "id": "6787f0a1576d53edaa4ac2c4", 1115 | "country_iso_code": "AT", 1116 | "gender": "Female" 1117 | }, 1118 | { 1119 | "label": "DACH - General", 1120 | "id": "67878805d5756f5ea31f4937", 1121 | "country_iso_code": "XC", 1122 | "gender": "All" 1123 | }, 1124 | { 1125 | "label": "DACH - Male", 1126 | "id": "6787880cd5756f5ea31f494d", 1127 | "country_iso_code": "XC", 1128 | "gender": "Male" 1129 | }, 1130 | { 1131 | "label": "DACH - Female", 1132 | "id": "678787cc756528e9736b27cc", 1133 | "country_iso_code": "XC", 1134 | "gender": "Female" 1135 | }, 1136 | { 1137 | "label": "Sweden - General", 1138 | "id": "6787e6c2b3515d0ec5e584f5", 1139 | "country_iso_code": "SE", 1140 | "gender": "All" 1141 | }, 1142 | { 1143 | "label": "Sweden - Male", 1144 | "id": "6787e55eb3515d0ec5e584df", 1145 | "country_iso_code": "SE", 1146 | "gender": "Male" 1147 | }, 1148 | { 1149 | "label": "Sweden - Female", 1150 | "id": "6787e4a4e41df35a860cf1f3", 1151 | "country_iso_code": "SE", 1152 | "gender": "Female" 1153 | }, 1154 | { 1155 | "label": "Denmark - General", 1156 | "id": "6787e32a576d53edaa4ac23f", 1157 | "country_iso_code": "DK", 1158 | "gender": "All" 1159 | }, 1160 | { 1161 | "label": "Denmark - Male", 1162 | "id": "6787e31ee41df35a860cf1db", 1163 | "country_iso_code": "DK", 1164 | "gender": "Male" 1165 | }, 1166 | { 1167 | "label": "Denmark - Female", 1168 | "id": "6787e1ee07bee09211af058f", 1169 | "country_iso_code": "DK", 1170 | "gender": "Female" 1171 | }, 1172 | { 1173 | "label": "Finland - General", 1174 | "id": "6787e20607bee09211af05a5", 1175 | "country_iso_code": "FI", 1176 | "gender": "All" 1177 | }, 1178 | { 1179 | "label": "Finland - Male", 1180 | "id": "6788c8e8caa3fa2d01766ca2", 1181 | "country_iso_code": "FI", 1182 | "gender": "Male" 1183 | }, 1184 | { 1185 | "label": "Finland - Female", 1186 | "id": "6787e0ea576d53edaa4ac229", 1187 | "country_iso_code": "FI", 1188 | "gender": "Female" 1189 | }, 1190 | { 1191 | "label": "Iceland - General", 1192 | "id": "6787e0a707bee09211af0579", 1193 | "country_iso_code": "IS", 1194 | "gender": "All" 1195 | }, 1196 | { 1197 | "label": "Iceland - Male", 1198 | "id": "6787e08c07bee09211af0563", 1199 | "country_iso_code": "IS", 1200 | "gender": "Male" 1201 | }, 1202 | { 1203 | "label": "Iceland - Female", 1204 | "id": "6787dfe3b3515d0ec5e584c7", 1205 | "country_iso_code": "IS", 1206 | "gender": "Female" 1207 | }, 1208 | { 1209 | "label": "Thailand - General", 1210 | "id": "6788caffcaa3fa2d01766cce", 1211 | "country_iso_code": "TH", 1212 | "gender": "All" 1213 | }, 1214 | { 1215 | "label": "Thailand - Male", 1216 | "id": "6787d196e41df35a860cf161", 1217 | "country_iso_code": "TH", 1218 | "gender": "Male" 1219 | }, 1220 | { 1221 | "label": "Thailand - Female", 1222 | "id": "6787d18fb3515d0ec5e58417", 1223 | "country_iso_code": "TH", 1224 | "gender": "Female" 1225 | }, 1226 | { 1227 | "label": "Kuwait - General", 1228 | "id": "678808464116af8da0809cfb", 1229 | "country_iso_code": "KW", 1230 | "gender": "All" 1231 | }, 1232 | { 1233 | "label": "Kuwait - Male", 1234 | "id": "678807814116af8da0809ce5", 1235 | "country_iso_code": "KW", 1236 | "gender": "Male" 1237 | }, 1238 | { 1239 | "label": "Kuwait - Female", 1240 | "id": "6788071884295fd198d9ae69", 1241 | "country_iso_code": "KW", 1242 | "gender": "Female" 1243 | }, 1244 | { 1245 | "label": "Lebanon - General", 1246 | "id": "6788068384295fd198d9ae53", 1247 | "country_iso_code": "LB", 1248 | "gender": "All" 1249 | }, 1250 | { 1251 | "label": "Lebanon - Male", 1252 | "id": "678805684116af8da0809ca3", 1253 | "country_iso_code": "LB", 1254 | "gender": "Male" 1255 | }, 1256 | { 1257 | "label": "Lebanon - Female", 1258 | "id": "6788064e4116af8da0809ccf", 1259 | "country_iso_code": "LB", 1260 | "gender": "Female" 1261 | }, 1262 | { 1263 | "label": "Oman - General", 1264 | "id": "6788045107bee09211af06d5", 1265 | "country_iso_code": "OM", 1266 | "gender": "All" 1267 | }, 1268 | { 1269 | "label": "Oman - Male", 1270 | "id": "6788044d84295fd198d9ae1a", 1271 | "country_iso_code": "OM", 1272 | "gender": "Male" 1273 | }, 1274 | { 1275 | "label": "Oman - Female", 1276 | "id": "678803dbe41df35a860cf364", 1277 | "country_iso_code": "OM", 1278 | "gender": "Female" 1279 | }, 1280 | { 1281 | "label": "Qatar - General", 1282 | "id": "67880373e41df35a860cf34e", 1283 | "country_iso_code": "QA", 1284 | "gender": "All" 1285 | }, 1286 | { 1287 | "label": "Qatar - Male", 1288 | "id": "67880381b3515d0ec5e585fa", 1289 | "country_iso_code": "QA", 1290 | "gender": "Male" 1291 | }, 1292 | { 1293 | "label": "Qatar - Female", 1294 | "id": "678802dae41df35a860cf338", 1295 | "country_iso_code": "QA", 1296 | "gender": "Female" 1297 | }, 1298 | { 1299 | "label": "West Bank & Gaza - General", 1300 | "id": "6788005a07bee09211af06b9", 1301 | "country_iso_code": "PS", 1302 | "gender": "All" 1303 | }, 1304 | { 1305 | "label": "West Bank & Gaza - Male", 1306 | "id": "6787fdbd07bee09211af06a3", 1307 | "country_iso_code": "PS", 1308 | "gender": "Male" 1309 | }, 1310 | { 1311 | "label": "West Bank & Gaza - Female", 1312 | "id": "6787fdcbb3515d0ec5e58580", 1313 | "country_iso_code": "PS", 1314 | "gender": "Female" 1315 | }, 1316 | { 1317 | "label": "Yemen - General", 1318 | "id": "6787fbad3f776e42a9077c9f", 1319 | "country_iso_code": "YE", 1320 | "gender": "All" 1321 | }, 1322 | { 1323 | "label": "Yemen - Male", 1324 | "id": "6787fba2b3515d0ec5e5856a", 1325 | "country_iso_code": "YE", 1326 | "gender": "Male" 1327 | }, 1328 | { 1329 | "label": "Yemen - Female", 1330 | "id": "6787fb1f3f776e42a9077c89", 1331 | "country_iso_code": "YE", 1332 | "gender": "Female" 1333 | }, 1334 | { 1335 | "label": "Saudi Arabia - General", 1336 | "id": "67880268b3515d0ec5e585c4", 1337 | "country_iso_code": "SA", 1338 | "gender": "All" 1339 | }, 1340 | { 1341 | "label": "Saudi Arabia - Male", 1342 | "id": "678802c5e41df35a860cf322", 1343 | "country_iso_code": "SA", 1344 | "gender": "Male" 1345 | }, 1346 | { 1347 | "label": "Saudi Arabia - Female", 1348 | "id": "6788007cb3515d0ec5e585a0", 1349 | "country_iso_code": "SA", 1350 | "gender": "Female" 1351 | }, 1352 | { 1353 | "label": "Nordics - General", 1354 | "id": "67878ab5e41df35a860ceeec", 1355 | "country_iso_code": "XN", 1356 | "gender": "All" 1357 | }, 1358 | { 1359 | "label": "Nordics - Male", 1360 | "id": "67878abbe41df35a860cef02", 1361 | "country_iso_code": "XN", 1362 | "gender": "Male" 1363 | }, 1364 | { 1365 | "label": "Nordics - Female", 1366 | "id": "67878aa9756528e9736b27e2", 1367 | "country_iso_code": "XN", 1368 | "gender": "Female" 1369 | }, 1370 | { 1371 | "label": "Portugal - General", 1372 | "id": "6787d0de576d53edaa4ac17d", 1373 | "country_iso_code": "PT", 1374 | "gender": "All" 1375 | }, 1376 | { 1377 | "label": "Portugal - Male", 1378 | "id": "6787d03de41df35a860cf14b", 1379 | "country_iso_code": "PT", 1380 | "gender": "Male" 1381 | }, 1382 | { 1383 | "label": "Portugal - Female", 1384 | "id": "6787cff2b3515d0ec5e58401", 1385 | "country_iso_code": "PT", 1386 | "gender": "Female" 1387 | }, 1388 | { 1389 | "label": "Bulgaria - General", 1390 | "id": "6787ce09e41df35a860cf109", 1391 | "country_iso_code": "BG", 1392 | "gender": "All" 1393 | }, 1394 | { 1395 | "label": "Bulgaria - Male", 1396 | "id": "6787ce06d5756f5ea31f4bd5", 1397 | "country_iso_code": "BG", 1398 | "gender": "Male" 1399 | }, 1400 | { 1401 | "label": "Bulgaria - Female", 1402 | "id": "6787cdd7e41df35a860cf0f3", 1403 | "country_iso_code": "BG", 1404 | "gender": "Female" 1405 | }, 1406 | { 1407 | "label": "Greece - General", 1408 | "id": "6787cd48576d53edaa4ac151", 1409 | "country_iso_code": "GR", 1410 | "gender": "All" 1411 | }, 1412 | { 1413 | "label": "Greece - Male", 1414 | "id": "6787cd28d5756f5ea31f4bbf", 1415 | "country_iso_code": "GR", 1416 | "gender": "Male" 1417 | }, 1418 | { 1419 | "label": "Greece - Female", 1420 | "id": "6787cd23d5756f5ea31f4ba9", 1421 | "country_iso_code": "GR", 1422 | "gender": "Female" 1423 | }, 1424 | { 1425 | "label": "Slovenia - General", 1426 | "id": "6787cc36d5756f5ea31f4b93", 1427 | "country_iso_code": "SI", 1428 | "gender": "All" 1429 | }, 1430 | { 1431 | "label": "Slovenia - Male", 1432 | "id": "6787cc0ce41df35a860cf0dd", 1433 | "country_iso_code": "SI", 1434 | "gender": "Male" 1435 | }, 1436 | { 1437 | "label": "Slovenia - Female", 1438 | "id": "6787cbd7d5756f5ea31f4b7b", 1439 | "country_iso_code": "SI", 1440 | "gender": "Female" 1441 | }, 1442 | { 1443 | "label": "Croatia - General", 1444 | "id": "6787cee1576d53edaa4ac167", 1445 | "country_iso_code": "HR", 1446 | "gender": "All" 1447 | }, 1448 | { 1449 | "label": "Croatia - Male", 1450 | "id": "6787ceade41df35a860cf11f", 1451 | "country_iso_code": "HR", 1452 | "gender": "Male" 1453 | }, 1454 | { 1455 | "label": "Croatia - Female", 1456 | "id": "6787ce7a22cefde60e48f6dd", 1457 | "country_iso_code": "HR", 1458 | "gender": "Female" 1459 | }, 1460 | { 1461 | "label": "Malaysia - General", 1462 | "id": "6787cb98576d53edaa4ac13b", 1463 | "country_iso_code": "MY", 1464 | "gender": "All" 1465 | }, 1466 | { 1467 | "label": "Malaysia - Male", 1468 | "id": "6787cbd422cefde60e48f6c0", 1469 | "country_iso_code": "MY", 1470 | "gender": "Male" 1471 | }, 1472 | { 1473 | "label": "Malaysia - Female", 1474 | "id": "6787cb64d5756f5ea31f4b65", 1475 | "country_iso_code": "MY", 1476 | "gender": "Female" 1477 | }, 1478 | { 1479 | "label": "Nigeria - General", 1480 | "id": "6787c95f22cefde60e48f692", 1481 | "country_iso_code": "NG", 1482 | "gender": "All" 1483 | }, 1484 | { 1485 | "label": "Nigeria - Male", 1486 | "id": "6787c918d5756f5ea31f4b45", 1487 | "country_iso_code": "NG", 1488 | "gender": "Male" 1489 | }, 1490 | { 1491 | "label": "Nigeria - Female", 1492 | "id": "6787c902d5756f5ea31f4b2f", 1493 | "country_iso_code": "NG", 1494 | "gender": "Female" 1495 | }, 1496 | { 1497 | "label": "Mali - General", 1498 | "id": "6787c67d22cefde60e48f67c", 1499 | "country_iso_code": "ML", 1500 | "gender": "All" 1501 | }, 1502 | { 1503 | "label": "Mali - Male", 1504 | "id": "6787c634d5756f5ea31f4b17", 1505 | "country_iso_code": "ML", 1506 | "gender": "Male" 1507 | }, 1508 | { 1509 | "label": "Mali - Female", 1510 | "id": "6787c5abd5756f5ea31f4b01", 1511 | "country_iso_code": "ML", 1512 | "gender": "Female" 1513 | }, 1514 | { 1515 | "label": "New Zealand - General", 1516 | "id": "6787c73f576d53edaa4ac10f", 1517 | "country_iso_code": "NZ", 1518 | "gender": "All" 1519 | }, 1520 | { 1521 | "label": "New Zealand - Male", 1522 | "id": "6787c5f722cefde60e48f666", 1523 | "country_iso_code": "NZ", 1524 | "gender": "Male" 1525 | }, 1526 | { 1527 | "label": "New Zealand - Female", 1528 | "id": "6787c52522cefde60e48f650", 1529 | "country_iso_code": "NZ", 1530 | "gender": "Female" 1531 | }, 1532 | { 1533 | "label": "North America and Europe - General", 1534 | "id": "67879246756528e9736b2802", 1535 | "country_iso_code": "XU", 1536 | "gender": "All" 1537 | }, 1538 | { 1539 | "label": "North America and Europe - Male", 1540 | "id": "6787925c756528e9736b2818", 1541 | "country_iso_code": "XU", 1542 | "gender": "Male" 1543 | }, 1544 | { 1545 | "label": "North America and Europe - Female", 1546 | "id": "6787924322cefde60e48f4dd", 1547 | "country_iso_code": "XU", 1548 | "gender": "Female" 1549 | }, 1550 | { 1551 | "label": "Sub-Saharan Africa - General", 1552 | "id": "678794a822cefde60e48f509", 1553 | "country_iso_code": "XF", 1554 | "gender": "All" 1555 | }, 1556 | { 1557 | "label": "Sub-Saharan Africa - Male", 1558 | "id": "678794ef756528e9736b282e", 1559 | "country_iso_code": "XF", 1560 | "gender": "Male" 1561 | }, 1562 | { 1563 | "label": "Sub-Saharan Africa - Female", 1564 | "id": "67879510756528e9736b2844", 1565 | "country_iso_code": "XF", 1566 | "gender": "Female" 1567 | }, 1568 | { 1569 | "label": "Asia Pacific - General", 1570 | "id": "67879707e41df35a860cef7f", 1571 | "country_iso_code": "XP", 1572 | "gender": "All" 1573 | }, 1574 | { 1575 | "label": "Asia Pacific - Male", 1576 | "id": "67879717e41df35a860cef95", 1577 | "country_iso_code": "XP", 1578 | "gender": "Male" 1579 | }, 1580 | { 1581 | "label": "Asia Pacific - Female", 1582 | "id": "6788cab196d815b07213eb41", 1583 | "country_iso_code": "XP", 1584 | "gender": "Female" 1585 | }, 1586 | { 1587 | "label": "Democratic Republic of the Congo - General", 1588 | "id": "6787c346d5756f5ea31f4aeb", 1589 | "country_iso_code": "CD", 1590 | "gender": "All" 1591 | }, 1592 | { 1593 | "label": "Democratic Republic of the Congo - Male", 1594 | "id": "6787c322d5756f5ea31f4ad5", 1595 | "country_iso_code": "CD", 1596 | "gender": "Male" 1597 | }, 1598 | { 1599 | "label": "Democratic Republic of the Congo - Female", 1600 | "id": "6787c299d5756f5ea31f4abf", 1601 | "country_iso_code": "CD", 1602 | "gender": "Female" 1603 | }, 1604 | { 1605 | "label": "Republic of the Congo - General", 1606 | "id": "6787c23d576d53edaa4ac0b7", 1607 | "country_iso_code": "CG", 1608 | "gender": "All" 1609 | }, 1610 | { 1611 | "label": "Republic of the Congo - Male", 1612 | "id": "6787c1ec576d53edaa4ac0a1", 1613 | "country_iso_code": "CG", 1614 | "gender": "Male" 1615 | }, 1616 | { 1617 | "label": "Republic of the Congo - Female", 1618 | "id": "6787c197576d53edaa4ac08b", 1619 | "country_iso_code": "CG", 1620 | "gender": "Female" 1621 | }, 1622 | { 1623 | "label": "Europe - Male", 1624 | "id": "6374cd49977ee9133ea034cc", 1625 | "country_iso_code": "XE", 1626 | "gender": "Male" 1627 | }, 1628 | { 1629 | "label": "Europe - Female", 1630 | "id": "6374cd28977ee9133ea034cb", 1631 | "country_iso_code": "XE", 1632 | "gender": "Female" 1633 | }, 1634 | { 1635 | "label": "Europe - General", 1636 | "id": "6374cde40173260e2d1ea2e0", 1637 | "country_iso_code": "XE", 1638 | "gender": "All" 1639 | }, 1640 | { 1641 | "label": "North America - Male", 1642 | "id": "6374d0c40173260e2d1ea2e4", 1643 | "country_iso_code": "NA", 1644 | "gender": "Male" 1645 | }, 1646 | { 1647 | "label": "North America - Female", 1648 | "id": "6374d0a50173260e2d1ea2e3", 1649 | "country_iso_code": "NA", 1650 | "gender": "Female" 1651 | }, 1652 | { 1653 | "label": "North America - General", 1654 | "id": "6374d104977ee9133ea034cf", 1655 | "country_iso_code": "NA", 1656 | "gender": "All" 1657 | }, 1658 | { 1659 | "label": "Honduras - General", 1660 | "id": "6787c2da576d53edaa4ac0cd", 1661 | "country_iso_code": "HN", 1662 | "gender": "All" 1663 | }, 1664 | { 1665 | "label": "Honduras - Male", 1666 | "id": "6787c27522cefde60e48f63a", 1667 | "country_iso_code": "HN", 1668 | "gender": "Male" 1669 | }, 1670 | { 1671 | "label": "Honduras - Female", 1672 | "id": "6788c9c296d815b07213eb15", 1673 | "country_iso_code": "HN", 1674 | "gender": "Female" 1675 | }, 1676 | { 1677 | "label": "Nicaragua - General", 1678 | "id": "6787b680d5756f5ea31f4a89", 1679 | "country_iso_code": "NI", 1680 | "gender": "All" 1681 | }, 1682 | { 1683 | "label": "Nicaragua - Male", 1684 | "id": "6787b5a6756528e9736b2920", 1685 | "country_iso_code": "NI", 1686 | "gender": "Male" 1687 | }, 1688 | { 1689 | "label": "Nicaragua - Female", 1690 | "id": "6787b4a022cefde60e48f622", 1691 | "country_iso_code": "NI", 1692 | "gender": "Female" 1693 | }, 1694 | { 1695 | "label": "Costa Rica - General", 1696 | "id": "6788cb0596d815b07213eb57", 1697 | "country_iso_code": "CR", 1698 | "gender": "All" 1699 | }, 1700 | { 1701 | "label": "Costa Rica - Male", 1702 | "id": "6787a548e41df35a860cf02f", 1703 | "country_iso_code": "CR", 1704 | "gender": "Male" 1705 | }, 1706 | { 1707 | "label": "Costa Rica - Female", 1708 | "id": "6787a48ee41df35a860cf019", 1709 | "country_iso_code": "CR", 1710 | "gender": "Female" 1711 | }, 1712 | { 1713 | "label": "Panama - General", 1714 | "id": "6787b3e8756528e9736b2908", 1715 | "country_iso_code": "PA", 1716 | "gender": "All" 1717 | }, 1718 | { 1719 | "label": "Panama - Male", 1720 | "id": "6787b27822cefde60e48f60c", 1721 | "country_iso_code": "PA", 1722 | "gender": "Male" 1723 | }, 1724 | { 1725 | "label": "Panama - Female", 1726 | "id": "6787b0b522cefde60e48f5f6", 1727 | "country_iso_code": "PA", 1728 | "gender": "Female" 1729 | }, 1730 | { 1731 | "label": "Dominican Republic - General", 1732 | "id": "6787ab14d5756f5ea31f4a69", 1733 | "country_iso_code": "DO", 1734 | "gender": "All" 1735 | }, 1736 | { 1737 | "label": "Dominican Republic - Male", 1738 | "id": "6787a8e5756528e9736b28b0", 1739 | "country_iso_code": "DO", 1740 | "gender": "Male" 1741 | }, 1742 | { 1743 | "label": "Dominican Republic - Female", 1744 | "id": "6787a6efd5756f5ea31f4a49", 1745 | "country_iso_code": "DO", 1746 | "gender": "Female" 1747 | }, 1748 | { 1749 | "label": "Taiwan - General", 1750 | "id": "6787adc5756528e9736b28dc", 1751 | "country_iso_code": "TW", 1752 | "gender": "All" 1753 | }, 1754 | { 1755 | "label": "Taiwan - Male", 1756 | "id": "6787ac8be41df35a860cf069", 1757 | "country_iso_code": "TW", 1758 | "gender": "Male" 1759 | }, 1760 | { 1761 | "label": "Taiwan - Female", 1762 | "id": "6787abe3756528e9736b28c6", 1763 | "country_iso_code": "TW", 1764 | "gender": "Female" 1765 | }, 1766 | { 1767 | "label": "Cuba - General", 1768 | "id": "6787af74756528e9736b28f2", 1769 | "country_iso_code": "CU", 1770 | "gender": "All" 1771 | }, 1772 | { 1773 | "label": "Cuba - Male", 1774 | "id": "6787af00e41df35a860cf07f", 1775 | "country_iso_code": "CU", 1776 | "gender": "Male" 1777 | }, 1778 | { 1779 | "label": "Cuba - Female", 1780 | "id": "6787ae3d22cefde60e48f5de", 1781 | "country_iso_code": "CU", 1782 | "gender": "Female" 1783 | }, 1784 | { 1785 | "label": "Belize - General", 1786 | "id": "6787a37b756528e9736b2872", 1787 | "country_iso_code": "BZ", 1788 | "gender": "All" 1789 | }, 1790 | { 1791 | "label": "Belize - Male", 1792 | "id": "6787a31222cefde60e48f57e", 1793 | "country_iso_code": "BZ", 1794 | "gender": "Male" 1795 | }, 1796 | { 1797 | "label": "Belize - Female", 1798 | "id": "6787a26b22cefde60e48f568", 1799 | "country_iso_code": "BZ", 1800 | "gender": "Female" 1801 | }, 1802 | { 1803 | "label": "Philippines - General", 1804 | "id": "6787a41ce41df35a860cf003", 1805 | "country_iso_code": "PH", 1806 | "gender": "All" 1807 | }, 1808 | { 1809 | "label": "Philippines - Male", 1810 | "id": "6787a1efe41df35a860cefe3", 1811 | "country_iso_code": "PH", 1812 | "gender": "Male" 1813 | }, 1814 | { 1815 | "label": "Philippines - Female", 1816 | "id": "6787a1cae41df35a860cefcd", 1817 | "country_iso_code": "PH", 1818 | "gender": "Female" 1819 | }, 1820 | { 1821 | "label": "Australia and New Zealand - Male", 1822 | "id": "6581e2eb68f5f63c37c49348", 1823 | "country": "NA", 1824 | "gender": "Male" 1825 | }, 1826 | { 1827 | "label": "Australia and New Zealand - Female", 1828 | "value": "6581e2eb87eeaf5be2da8556", 1829 | "country": "NA", 1830 | "gender": "Female" 1831 | }, 1832 | { 1833 | "label": "Australia and New Zealand - General", 1834 | "value": "6581e2e487eeaf5be2da8554", 1835 | "country": "NA", 1836 | "gender": "All" 1837 | } 1838 | ]; 1839 | 1840 | 1841 | 1842 | --------------------------------------------------------------------------------