Canonical market reference data platform for TradingGoose
Market data cockpit for canonical listings, exchanges, currencies, and trading hours.
TradingGoose Market is the canonical source of truth for market reference data used across TradingGoose. It stores and serves listings, exchanges, cryptocurrencies, currencies, countries, cities, time zones, blockchain networks, market groups, and trading hours through a Next.js admin UI and a versioned public API.
It exists because of a problem we ran into while building TradingGoose Studio: once Studio needed to support more than one market data provider, symbol identity stopped being simple. The same asset could be spelled differently across Yahoo Finance, Alpaca, Finnhub, Alpha Vantage, and other sources, which made cross-provider support harder than it should have been.
Early Stage Notice
TradingGoose Market is still under active development. Expect rough edges, schema changes, and occasional breaking updates while the platform evolves.
TradingGoose Market was built to give TradingGoose Studio a shared market identity layer instead of pushing provider-specific symbol logic into every connector. Market keeps the canonical records and market metadata. Studio can then apply provider-specific symbol formatting rules on top of that shared context.
If you want the full background, see:
- Canonical management of market reference data.
- Versioned public API at
/api/search,/api/get, and/api/update, with short-path rewrites for/searchand/update. - Admin UI for browse, create, edit, export, and upload flows across every entity type.
- Team management and invitation-based signup flows for admin and collaborators.
- API key support with per-key rate limiting and usage reporting back to TradingGoose Studio.
- Icon upload support with local filesystem, Vercel Blob, or Azure Blob storage.
- Optional plugin injection through
MARKET_PLUGIN_MODULES.
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Framework | Next.js (App Router) |
| Database | PostgreSQL + Drizzle ORM |
| Auth | Better Auth + HMAC-signed API keys |
| UI | Radix UI, shadcn/ui, Tailwind CSS, TanStack Table |
| Storage | Local filesystem, Vercel Blob, or Azure Blob |
| Integrations | Redis, Resend, TradingGoose Studio billing hooks |
| Language | TypeScript |
- Bun 1.3+
- Docker or an existing PostgreSQL instance
- PostgreSQL 17 recommended
-
Copy the environment template.
cp .env.example .env
-
Install dependencies.
bun install
-
Start PostgreSQL. Example:
docker run -d --name tradinggoose-market-db -p 5432:5432 \ -e POSTGRES_PASSWORD=password \ -e POSTGRES_DB=tradinggoose \ postgres:17
-
Fill in the required values in
.env. -
Run migrations.
bun run db:migrate
-
Start the app.
bun run dev
-
Open
http://localhost:3000/admin.
The first user to sign up becomes an admin. After that, sign-up is restricted to invited users.
Required for a normal local setup:
DATABASE_URL- used by Drizzle Kit migrations and as the runtime fallback connection string.BETTER_AUTH_SECRET- auth and session secret.BETTER_AUTH_URL- auth base URL.NEXT_PUBLIC_APP_URL- public app URL used for auth redirects and generated links.INTERNAL_API_SECRET- HMAC pepper and internal API secret.
Optional or integration-specific:
- Runtime DB tuning:
DATABASE_POOL_URL,DATABASE_POOL_MAX. - Free-tier and rate limiting:
REDIS_URL,MARKET_FREE_TIER_*. - Rank update access:
MARKET_RANK_UPDATE_ACCESS_MODE(authenticatedorservice). - Email delivery:
RESEND_API_KEY, optionalFROM_EMAIL_ADDRESS. - TradingGoose Studio billing integration:
OFFICIAL_TG_URL. - Storage and plugins:
STORAGE_SERVICE, cloud storage credentials,MARKET_PLUGIN_MODULES,MARKET_PLUGIN_SOURCES.
If you use DATABASE_POOL_URL at runtime, keep DATABASE_URL set for migrations.
If STORAGE_SERVICE is not set, the app auto-detects storage from the configured credentials and falls back to local storage when no cloud provider is configured.
TradingGoose Market can inject private or local plugins at install time without committing their dependencies.
Example:
MARKET_PLUGIN_MODULES=@your-org/your-plugin
MARKET_PLUGIN_SOURCES={"@your-org/your-plugin":"file:../your-plugin"}
bun run install:with-plugins
bun run devbun run install:with-plugins writes the generated plugin loader, installs the requested modules, and restores package.json afterward. Restart the dev server after rerunning it so the generated loader is picked up.
app/
(auth)/ Login and signup pages
admin/ Admin UI pages and CRUD screens
api/ Auth, health, search, get, update, uploads, and export routes
lib/
auth/ Better Auth server and client config
db/ Database client utilities and status checks
market-api/ API auth, rate limiting, billing, and versioned handlers
ui/ Shared UI utilities
packages/
db/ Drizzle schema, client, and migrations package
uploads/ Storage abstraction for local, Vercel Blob, and Azure Blob
scripts/ Install-time plugin injector
The public API is available through /api/search, /api/get, and /api/update. The shorter /search and /update paths are rewritten to the same handlers.
Authenticated requests are validated with HMAC-signed keys, and rate limits are enforced per key.
| Endpoint | Description |
|---|---|
GET /search |
Universal search across listings, cryptos, and currencies |
GET /search/listings |
Search listings with filters |
GET /search/cryptos |
Search cryptocurrencies with chain and pair filters |
GET /search/currencies |
Search currencies by code or name |
GET /search/exchanges |
Search exchanges by MIC or name |
GET /search/countries |
Search countries by code or name |
GET /search/cities |
Search cities by name or country |
| Endpoint | Description |
|---|---|
GET /get/listing |
Fetch listing(s) by ID, single or batch up to 200 |
GET /get/crypto |
Fetch crypto(s) by ID, single or batch up to 200 |
GET /get/currency |
Fetch currency(ies) by ID, single or batch up to 200 |
GET /get/timezone |
Fetch time zone info |
GET /get/market-hours |
Fetch trading hours for an exchange or market |
| Endpoint | Description |
|---|---|
POST /update/listing-rank |
Update listing rank, with /decay variant |
POST /update/listing-logo |
Update listing logo |
POST /update/crypto-rank |
Update crypto rank, with /decay variant |
POST /update/crypto-logo |
Update crypto logo |
POST /update/currency-rank |
Update currency rank, with /decay variant |
POST /update/currency-logo |
Update currency logo |
POST /update/country-logo |
Update country logo |
Rate limits default to 50 req/s per user key and 1,000 req/s for internal service keys. When free tier access is enabled, unauthenticated requests are rate limited by IP, with defaults of 25 req/min and 500 req/day.
Each entity (listings, cryptos, currencies, exchanges, countries, cities, chains, markets, time-zones, market-hours) exposes:
| Method | Path | Description |
|---|---|---|
GET |
/api/{entity} |
List with pagination and filters, plus option mode for dropdowns |
POST |
/api/{entity} |
Create |
GET |
/api/{entity}/{id} |
Get single |
PATCH |
/api/{entity}/{id} |
Update |
DELETE |
/api/{entity}/{id} |
Delete |
GET |
/api/{entity}/export |
Export all as JSON |
| Command | Description |
|---|---|
bun run dev |
Start the development server |
bun run build |
Create a production build |
bun run start |
Start the production server |
bun run lint |
Run ESLint |
bun run type-check |
Run TypeScript type checking |
bun run db:generate |
Generate Drizzle migration files |
bun run db:migrate |
Apply database migrations |
bun run install:with-plugins |
Install dependencies with plugin injection enabled |
TradingGoose Market is licensed under Apache-2.0. See the LICENSE file for details.