Audience DNA Profiler is a Next.js application for operational Instagram audience intelligence. It analyzes audience signals, clusters followers into actionable personas, and scores draft posts against those personas before publishing.
The project is built as a hackathon-style MVP with a safe mock mode by default and an optional live mode backed by Apify, Supabase, and fal/OpenRouter.
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- Environment Variables
- Data Modes
- Database Setup
- Available Scripts
- API Routes
- Project Structure
- Operational Notes
Audience DNA Profiler helps social teams understand who their Instagram audience appears to be, what those segments care about, and whether a proposed post is likely to match the current audience profile.
The app currently includes:
- A dashboard for audience analysis and persona breakdowns.
- A draft content scoring workflow with caption and image input.
- Mock fixtures for reliable local demos.
- Optional live Instagram ingestion through Apify actors.
- Optional LLM-powered persona, insight, sentiment, and visual draft scoring through fal/OpenRouter.
- Optional persistence in Supabase.
- Audience segmentation: Converts follower, following, post, and comment signals into audience personas.
- Confidence-aware analysis: Treats private or sparse profiles as low-signal rather than inventing demographic assumptions.
- Operational insights: Summarizes sentiment, content themes, audience questions, recommendations, and post ideas.
- Draft compatibility scoring: Scores captions and uploaded images against the current audience personas.
- Mock-to-live workflow: Runs without external services by default, then switches to live data when required environment variables are present.
- Provider isolation: Keeps Instagram scraping behind server-side adapters so UI and intelligence code do not depend directly on provider payloads.
- Supabase persistence: Stores organizations, followers, posts, interactions, personas, analysis runs, and draft reports when configured.
| Area | Technology |
|---|---|
| Framework | Next.js 16 App Router |
| UI | React 19, Tailwind CSS 4, shadcn/ui, Radix UI, lucide-react |
| Validation | Zod |
| Data provider | Apify Client |
| Database | Supabase |
| AI inference | fal/OpenRouter, default model google/gemini-2.5-flash |
| Language | TypeScript |
| Tooling | ESLint, npm |
app/
api/
analyze-audience/route.ts # POST endpoint for audience analysis
score-draft/route.ts # POST endpoint for draft scoring
layout.tsx # Root layout and metadata
page.tsx # Dashboard entry point
components/
audience-dashboard.tsx # Main interactive dashboard
draft-checker.tsx # Legacy/alternate draft scoring panel
ui/ # shadcn/ui primitives
lib/
instagram/ # Apify provider and normalization adapters
intelligence/ # Persona, insight, sentiment, and draft scoring logic
supabase/ # Supabase clients and persistence helpers
mock-data.ts # Local demo data
data-mode.ts # Mock/live mode selection
types.ts # Shared domain types
- Node.js 20 or newer
- npm
- Optional: Supabase project for persistence
- Optional: Apify account and token for live Instagram ingestion
- Optional: fal account and key for LLM and vision scoring
npm installnpm run devOpen http://localhost:3000 in your browser.
The application works in mock mode without external credentials.
Create a .env.local file in the project root. Do not commit this file.
| Variable | Description |
|---|---|
AUDIENCEDNA_DATA_MODE |
Set to live to enable live provider flow. Any other value uses mock data. |
AUDIENCEDNA_INTERNAL_API_KEY |
Optional internal bypass key for server-to-server access to protected API routes. |
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL. |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anon key for browser clients. |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key for server-side persistence. |
APIFY_TOKEN |
Apify API token for Instagram actors. |
| Variable | Description |
|---|---|
FAL_KEY |
Enables fal/OpenRouter persona generation, insights, sentiment enrichment, and draft scoring. |
FAL_OPENROUTER_MODEL |
Optional model override. Defaults to google/gemini-2.5-flash. |
| Variable | Default |
|---|---|
APIFY_INSTAGRAM_ACTOR_ID |
datadoping/instagram-followers-scraper |
APIFY_INSTAGRAM_MAX_COUNT |
50 |
APIFY_INSTAGRAM_POSTS_ACTOR_ID |
apify/instagram-api-scraper |
APIFY_INSTAGRAM_POSTS_LIMIT |
5 |
APIFY_INSTAGRAM_COMMENTS_ACTOR_ID |
apify/instagram-comment-scraper |
APIFY_INSTAGRAM_COMMENTS_LIMIT |
10 |
APIFY_INSTAGRAM_FOLLOWING_ACTOR_ID |
scrapebase/instagram-following-scraper |
APIFY_FOLLOWING_MAX_ITEMS |
25 |
APIFY_FOLLOWING_ENRICH_LIMIT |
5 |
INSTAGRAM_SESSION_ID |
Optional session value for following enrichment actor. |
Example:
AUDIENCEDNA_DATA_MODE=mock
AUDIENCEDNA_INTERNAL_API_KEY=
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
APIFY_TOKEN=
FAL_KEY=Mock mode is the default. It uses deterministic data from lib/mock-data.ts and is suitable for development, demos, and UI work.
Live mode is enabled only when all of the following are true:
AUDIENCEDNA_DATA_MODE=liveNEXT_PUBLIC_SUPABASE_URLis setSUPABASE_SERVICE_ROLE_KEYis setAPIFY_TOKENis set
If live analysis fails, the app falls back to mock data so the interface remains usable.
The public POST routes apply lightweight abuse protection:
- same-origin enforcement for browser requests
- persistent Supabase-backed IP rate limiting, with in-memory fallback if the table is missing
- request size checks for small analyze payloads and larger VLM score payloads
If you need trusted server-to-server access, set AUDIENCEDNA_INTERNAL_API_KEY and send it as x-audiencedna-internal-key.
The audience analysis route returns a minimized public DTO:
- personas and insights
- aggregate stats
- derived signal charts
- curated evidence items
Raw follower rows, raw followed-account lists, and raw interaction records are kept server-side and are not returned directly to the browser.
Supabase is optional for mock mode and required for durable live-mode persistence.
- Create a Supabase project.
- Run the full schema from
implementation-status.mdin the Supabase SQL editor. - Run or verify the supplemental tables in
supabase-next.sql. - Run
supabase-security.sqlto enable persistent API rate limiting. - Add the Supabase environment variables to
.env.local.
The current persistence layer expects these tables:
organizationsfollowerspost_interactionsinstagram_postsaudience_personasanalysis_runsdraft_reports
| Command | Description |
|---|---|
npm run dev |
Start the local development server. |
npm run build |
Build the production application. |
npm run start |
Start the production server after a build. |
npm run lint |
Run ESLint. |
Analyzes an Instagram handle and returns an AudienceSnapshot.
Request body:
{
"handle": "northstar.roasters"
}Scores a draft caption and optional image payload against an organization's personas.
Request body:
{
"organizationId": "org_audiencedna_demo",
"caption": "Finals week fuel and quiet tables until close.",
"imageDescription": "Cafe table with laptops and cold brew.",
"imageDataUrl": "data:image/png;base64,..."
}| Path | Purpose |
|---|---|
app/ |
Next.js App Router pages, layout, global styles, and route handlers. |
components/ |
Dashboard UI and reusable shadcn/ui components. |
lib/instagram/ |
Apify calls and provider payload normalization. |
lib/intelligence/ |
AI prompts, Zod schemas, persona generation, sentiment, insights, and scoring. |
lib/supabase/ |
Supabase client factories and persistence helpers. |
lib/mock-data.ts |
Local deterministic demo snapshot. |
old-ui-files/ |
Archived Streamlit/Python UI prototypes kept for reference. |
implementation-status.md |
Planning notes, original schema, and implementation history. |
supabase-next.sql |
Supplemental Supabase tables and indexes. |
- Provider tokens and Supabase service keys must stay server-side.
- Instagram scraping is intentionally delegated to Apify actors rather than browser/client code.
- Private profiles and missing fields reduce confidence; they are not treated as negative evidence.
- LLM outputs are validated with Zod before use.
- In-memory caching is used for the latest live snapshot during the current server process.
- Dashboard copy, generated insights, and documentation are maintained in English.