Open APIs, flexible embeds, and tools that keep shooters in sync.
Everything you need to power custom dashboards, automate match promotion, or embed the finder on your own site. Start with the public endpoints, request keys when you are ready, and ship faster with our community behind you.
API rate plans
Every key starts on the Community tier. When your integration needs more headroom, pick a plan and we’ll help you upgrade without breaking existing clients.
Community
$0Default tier for self-serve developers building dashboards, widgets, or local tools.
Best for: Personal keys (default)
Partner
Let’s chatHigher ceilings for approved partners syncing archives or powering public discovery tools.
Best for: Trusted integrations
Enterprise
CustomCommercial data products, national federations, and partners who need burst headroom.
Best for: Commercial workloads
Club, match, season, and series scoped keys already include conservative per-entity caps (20–60 requests per minute) to keep integrations honest. Reach out if you need a temporary boost.
Embed the match finder anywhere
Choose the integration path that matches your stack. All options pull the same real-time data that powers shootershub.fortneyengineering.com.
iFrame (fastest)
Hosted by The Shooters Hub. Set query parameters to pre-filter by type, sort order, or view.
<iframe src="https://shootershub.fortneyengineering.com/match-finder?type=NRL22&sort=dateAsc" loading="lazy" style="width:100%;height:720px;border:0;border-radius:18px;" allow="clipboard-read; clipboard-write; geolocation" ></iframe>
No API key required. The iframe automatically stays in sync with the latest public data.
Script widget
Lightweight helper that mounts the finder into any container. Configure filters through a JSON data attribute.
<div class="shmf" data-sh-config='{"type":["NRL22"],"view":"map","radius":150,"center":{"lat":39.95,"lng":-75.16}}'></div>
<script src="https://shootershub.fortneyengineering.com/embed/match-finder.js" defer></script>Include `type`, `view`, `radius`, and `center` (lat/lng) keys inside `data-sh-config`. The script can be self-hosted via the `build/match-finder.js` artifact.
WordPress plugin
Download the zip from the `FortneyManufacturingLLC/shooters-hub-wp` releases and drop the shortcode or block into any page.
[shooters_hub_match_finder type="NRL22" view="map" radius="200"]
The plugin registers both a shortcode and a “Shooters Hub: Match Finder” block, proxies API calls via your token, and can auto-update from GitHub.
HTTP API overview
Core read endpoints stay open so clubs, series, and third-party developers can build without friction. For write access or advanced reports, request a scoped API key.
/api/matchesQuery public matches
Returns match metadata straight from Firestore. Supports location filters, series/season logic, and results are cached for 30 seconds.
Filter by disciplines (e.g., `NRL22`, `PRS Rimfire`).
Inclusive start date for results.
Inclusive end date for results.
Center the search by coordinates. Combine with `radius` in miles.
Server-side postal lookup. Provide `country` (ISO code) when outside the US.
Filter by series IDs or slugs. Use `seriesMode=and` to require all values.
Filter by season IDs. Supports `seasonsMode=and` like series.
Sort order for the response. Distance sorting requires `lat` and `lng`.
Max results to return (defaults to 200, caps at 1000).
Example
curl "https://shootershub.fortneyengineering.com/api/matches?type=NRL22&from=2025-01-01&lat=43.12&lng=-90.45&radius=150&limit=50"
Response
{
"items": [
{
"id": "match_abcd1234",
"name": "Driftless Rimfire - January 2025",
"date": "2025-01-18",
"type": "NRL22",
"matchTier": "regional",
"status": "Open",
"location": { "lat": 43.12, "lng": -90.45, "name": "Driftless Rimfire Club" },
"distanceMi": 84.2,
"seriesIds": ["driftless-league"],
"seasonIds": ["2025-midwest-rimfire"],
"startTime": "08:30"
}
],
"nextCursor": ""
}/api/matches/bulkBatch hydrate matches
Look up many matches by document ID in a single call. Provide up to 200 IDs via `ids` and use an API key with the `matches.read` scope (or match-scoped variant) to include unpublished metadata you already have access to.
Comma/newline separated list of match IDs. Works as `?ids=match_a,match_b` for GET or `{ "ids": ["match_a", "match_b"] }` in POST. Duplicates and private matches are skipped.
Example
curl "https://shootershub.fortneyengineering.com/api/<your-api-key>/matches/bulk?ids=match_abcd1234,match_wxyz5678"
Response
{
"items": [
{
"id": "match_abcd1234",
"name": "Driftless Rimfire - January 2025",
"date": "2025-01-18",
"endDate": "2025-01-19",
"type": "NRL22",
"matchTier": "regional",
"status": "Open",
"clubId": "club_driftless",
"location": { "name": "Driftless Rimfire Club", "lat": 43.12, "lng": -90.45 },
"seriesIds": ["driftless-league"],
"seasonIds": ["2025-midwest-rimfire"],
"startTime": "08:30",
"documents": { "cofUrl": "https://example.com/driftless-jan-2025-cof.pdf" }
}
]
}/api/clubs/bulkBatch hydrate clubs
Returns sanitized club profiles, PractiScore metadata, and linked entities for IDs you already manage. Requires an API key carrying `clubs.read` (or a club-scoped variant) and respects the same rate limits shown in your key manager.
List of club document IDs. Submit as `?ids=club_alpha,club_beta` or JSON `{ "ids": [...] }`. Only public or administrator-visible clubs are returned.
Example
curl "https://shootershub.fortneyengineering.com/api/<your-api-key>/clubs/bulk?ids=club_driftless"
Response
{
"items": [
{
"id": "club_driftless",
"name": "Driftless Rimfire Club",
"bio": "Volunteer-led rimfire club serving southwest Wisconsin.",
"website": "https://driftlessrimfire.example.com",
"location": { "name": "Driftless Rimfire Range", "lat": 43.12, "lng": -90.45 },
"seasonIds": ["2025-midwest-rimfire"],
"matchIds": ["match_abcd1234"],
"practiscoreSlug": "driftless-rimfire",
"practiscoreLinks": { "website": "https://practiscore.com/clubs/driftless-rimfire" },
"public": true
}
]
}/api/series/bulkBatch hydrate series
Fetches public series with linked clubs, sponsor IDs, and configured defaults. Use an API key that includes `series.read` or a series-scoped grant to mirror the same information available in the dashboard.
Comma/newline separated series IDs or a JSON array body. Responses keep the ordering you pass in and omit non-public series.
Example
curl "https://shootershub.fortneyengineering.com/api/<your-api-key>/series/bulk?ids=driftless-league"
Response
{
"items": [
{
"id": "driftless-league",
"name": "Driftless Rimfire League",
"shortName": "Driftless League",
"slug": "driftless-rimfire-league",
"description": "Regional rimfire series pairing monthly club matches into a points race.",
"associationId": "assoc_midwest_rimfire",
"logoURL": "https://cdn.shootershub.app/series/driftless/logo.png",
"clubIds": ["club_driftless"],
"seasonIds": ["2025-midwest-rimfire"],
"links": { "website": "https://driftlessrimfire.example.com" },
"public": true
}
]
}/api/seasons/bulkBatch hydrate seasons
Hydrate season schedules, leaderboard rules, and linked matches in one request. Requires a key with `seasons.read` (or a season-specific grant) and only surfaces data visible to that scope.
Ordered list of season IDs to fetch. Accepts CSV, newline-delimited text, or JSON bodies. Private or hidden seasons are skipped automatically.
Example
curl "https://shootershub.fortneyengineering.com/api/<your-api-key>/seasons/bulk?ids=2025-midwest-rimfire"
Response
{
"items": [
{
"id": "2025-midwest-rimfire",
"name": "2025 Midwest Rimfire Season",
"seriesId": "driftless-league",
"startDate": "2025-01-01",
"endDate": "2025-12-31",
"leaderboardRules": {
"metric": "points",
"selection": { "mode": "bestOf", "topN": 8 },
"filters": { "allowedDivisions": ["Open", "Young Guns"] }
},
"matches": [
{ "matchId": "match_abcd1234", "include": true, "countsForScoring": true, "date": "2025-01-18" }
],
"classes": ["Open", "Base"],
"divisions": ["Young Guns", "Ladies"],
"public": true
}
]
}/api/users/bulkBatch hydrate shooter profiles
Returns public shooter profiles—including bios, social links, and opt-in home locations—for IDs you already have permission to view. Authenticate with an API key that carries the same read scopes you use in the dashboard.
Provide user IDs as `?ids=user_123,user_456` or `{ "ids": ["user_123", "user_456"] }`. Profiles marked private are filtered out automatically.
Example
curl "https://shootershub.fortneyengineering.com/api/<your-api-key>/users/bulk?ids=user_abc123"
Response
{
"items": [
{
"id": "user_abc123",
"displayName": "Jamie Marks",
"username": "jamie_marks",
"bio": "Rimfire competitor and match director.",
"photoURL": "https://cdn.shootershub.app/profiles/jamie.png",
"homeLocation": { "name": "Madison, WI", "lat": 43.07, "lng": -89.4 },
"gear": {
"binos": "Leupold BX-4",
"firearms": {
"rimfire": [
{ "optic": "Vortex Strike Eagle 3-18x", "chassis": "MDT ACC", "action": "Vudoo V-22" }
]
}
},
"links": { "website": "https://jamieshooting.example.com" },
"socials": { "instagram": "https://instagram.com/jamie.rimfire" }
}
]
}/api/olc/searchRetrieve tile coverage
Generates Open Location Code (OLC) tiles for the match finder. Use `mode=circle` with a lat/lng/radius or `mode=list` with explicit tile IDs. Responses include caching headers and ETags for conditional requests.
Whether to compute tiles from a circle or fetch specific tiles.
Circle center coordinates (required for `mode=circle`).
Circle radius in miles.
Limit tiles to specific months (defaults to the current month).
List of `OLC4:YYYY-MM` tile IDs to fetch when `mode=list`.
Optional server-side filters applied to tile contents.
Send cached tile timestamps to receive `notModified` entries instead of payloads.
Example
curl "https://shootershub.fortneyengineering.com/api/olc/search?mode=circle&lat=43.12&lng=-90.45&radiusMi=150&months=2025-01,2025-02"
Response
{
"tiles": [
{
"tileId": "86JQ:2025-01",
"olc4": "86JQ",
"yyyymm": "2025-01",
"updatedAt": "2024-12-10T04:12:11.000Z",
"count": 6,
"items": [
{ "id": "match_abcd1234", "title": "Driftless Rimfire - January 2025", "lat": 43.12, "lng": -90.45, "date": "2025-01-18" }
]
}
],
"notModified": [],
"missing": [],
"fulfilledTiles": ["86JQ:2025-01"]
}/api/olc/tilesBatch fetch tile details
Fetches specific tiles in bulk, honoring cache validation via `have`. Helpful when you already know which tiles are relevant and need the raw matches inside each tile.
Array of tile IDs such as `86JQ:2025-01`.
Client cache metadata to skip unchanged tiles.
Optional filters applied server-side before returning tile contents.
Example
curl -X POST https://shootershub.fortneyengineering.com/api/olc/tiles \
-H "Content-Type: application/json" \
-d '{"tileIds":["86JQ:2025-01","86JR:2025-01"]}'Response
{
"tiles": [
{ "tileId": "86JQ:2025-01", "count": 6, "items": [/* ... */] }
],
"notModified": [],
"missing": [],
"fulfilledTiles": ["86JQ:2025-01"]
}Test queries without leaving the page
The playground uses the same `apiUrl()` helper as the web app. Configure filters, send a request, and inspect the JSON response instantly.
Request URL (replace <your-api-key>)
https://shootershub.fortneyengineering.com/api/<your-api-key>/matches?type=NRL22&limit=50&sort=dateAsc&radius=150Production calls can send the same value as an x-api-key header instead of rewriting the path. Both styles consume the per-minute and per-day limits you configure in the API key manager, while this playground still hits our first-party host without adding a key so you can experiment safely.
cURL
curl "https://shootershub.fortneyengineering.com/api/<your-api-key>/matches?type=NRL22&limit=50&sort=dateAsc&radius=150"
Response
// Run the query to preview JSON output.
Authentication & scopes
GET /api/matches and every /api/olc/* route stay public. Private lookups require an API key—either send it as an x-api-key header or rewrite the path to /api/<your-api-key>/…. Functions remove the key segment before hitting the handler. Create, scope, and rotate keys from the API key manager to keep each integration isolated.
Rate limiting & caching
Public endpoints ship with Cache-Control headers (matches: 30s; OLC tiles: 5 min) plus ETags. Respect If-None-Match and watch the per-minute / per-day quotas displayed alongside each key in the manager—header and path-style authentication both draw from the same limits.
Local development
When running the Next.js app locally, set `NEXT_PUBLIC_API_BASE` to your Functions origin (or `/api` to proxy through the Next API route). The `apiUrl()` helper in `@/utils/api` builds the correct path for both environments.
Shipping something ambitious?
We partner with match directors, clubs, and integrators to launch custom experiences. Share your roadmap and we will line up the right APIs, caches, and billing model.