Quick, beginner-friendly guide for integrating with the SportFishTrader Broker Listing API using a single broker API key.
This API lets you create and manage your own SportFishTrader boat listings from your server using a single broker API key.
π Key Features:
x-api-key (or Authorization: Bearer ...)dryRun mode (no write)Important: Use listingStatus to control validation strictness. Draft creates accept minimal fields; active/scheduled creates require complete details (photos, engines, location, etc.).
Every request must include your broker API key. We recommend x-api-key; we also accept Authorization: Bearer <API_KEY>. API access is tied to your broker account and an active subscription.
Recommended headers: Send x-api-version: v1 on all requests. You can also send your own x-request-id (optional) and the API will echo it back as X-Request-Id. Include that request id when contacting support.
Idempotency: For POST /api/v1/create (non-dryRun), you can send x-idempotency-key to safely retry without double-creating.
curl -X GET https://www.sportfishtrader.com/api/v1/list?page=1&limit=25 -H "x-api-key: YOUR_BROKER_API_KEY_HERE" \
-H "x-api-version: v1" \
-H "x-request-id: YOUR_REQUEST_ID_OPTIONAL"β οΈ Security Warning: Never expose your API key in client-side code or public repositories. Always keep it secure on your server.
Use this tool to generate ready-to-copy request templates. It does not accept or test real keys (the examples always use YOUR_BROKER_API_KEY_HERE).
Generated request: POST https://www.sportfishtrader.com/api/v1/create?dryRun=true
curl -X POST "https://www.sportfishtrader.com/api/v1/create?dryRun=true" \
-H "x-api-key: YOUR_BROKER_API_KEY_HERE" \
-H "x-api-version: v1" \
-H "x-request-id: YOUR_REQUEST_ID_OPTIONAL" \
-H "Content-Type: application/json" \
-d '{
"listingStatus": "draft",
"year": 2018,
"manufacturer": "Viking",
"model": "52 Sport Tower"
}'const res = await fetch('https://www.sportfishtrader.com/api/v1/create?dryRun=true', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_BROKER_API_KEY_HERE',
'x-api-version': 'v1',
'x-request-id': 'YOUR_REQUEST_ID_OPTIONAL',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"listingStatus": "draft",
"year": 2018,
"manufacturer": "Viking",
"model": "52 Sport Tower"
})
});
const data = await res.json();
console.log(data);import requests
url = "https://www.sportfishtrader.com/api/v1/create?dryRun=true"
headers = {
"x-api-key": "YOUR_BROKER_API_KEY_HERE",
"x-api-version": "v1",
"x-request-id": "YOUR_REQUEST_ID_OPTIONAL",
"Content-Type": "application/json"
}
payload = {
"listingStatus": "draft",
"year": 2018,
"manufacturer": "Viking",
"model": "52 Sport Tower"
}
res = requests.request("POST", url, json=payload, headers=headers)
print(res.status_code)
print(res.json())Photos vs Media: Listings use top-level photos and featuredPhoto. The optional media object is used for videos and the media enhancement pipeline. You can omit media.photos; the API will mirror photos into it.
Broker listings only: Auction/marketplace and Voyage Photography fields are not part of the broker API contract.
/api/v1/create
Creates a new listing automatically attributed to your broker account. Use dryRun to validate without writing.
| Header | Value | Required |
|---|---|---|
x-api-key | Your broker API key (YOUR_BROKER_API_KEY_HERE) | Yes |
x-api-version | v1 | Optional |
x-request-id | Optional client request id | Optional |
x-idempotency-key | Optional (create only; non-dryRun) | Optional |
Content-Type | application/json | Yes |
| Field | Type | Required | Description |
|---|---|---|---|
listingStatus | string | Optional | draft | active | live | scheduled Example: draft |
scheduledAt | string|Date | Optional | Only when listingStatus=scheduled (ISO timestamp) Example: 2026-01-09T14:15:00-05:00 |
condition | string | Optional | New | Used (required when publishing) Example: Used |
year | number | Yes | Year Example: 2018 |
manufacturer | string | Yes | Builder/manufacturer Example: Viking |
model | string | Yes | Model Example: 52 Sport Tower |
type | string | Optional | Boat class/type (required when publishing) Example: Sportfish |
price | number | Optional | Required unless contactForPrice=true when publishing Example: 1250000 |
contactForPrice | boolean | Optional | If true, price can be omitted for publish Example: false |
locationCity | string | Optional | City Example: Fort Lauderdale |
locationState | string | Optional | State (required when publishing) Example: FL |
locationZIP | string | Optional | ZIP Example: 33301 |
length | number | Optional | Length (feet; required when publishing) Example: 52 |
beam | string | Optional | Beam (feet) Example: 16.5 |
draft | string | Optional | Draft (feet) Example: 4.5 |
cabins | string | Optional | Cabins Example: 2 |
heads | string | Optional | Heads Example: 2 |
capacity | string | Optional | Capacity Example: β |
cruiseSpeed | string | Optional | Cruise speed Example: β |
maxSpeed | string | Optional | Max speed Example: β |
holdingTank | string | Optional | Holding tank Example: β |
waterTank | string | Optional | Water tank Example: β |
description | string | Optional | Description (>= 50 chars required when publishing) |
photos | array | Optional | Photo URLs (>= 1 required when publishing) |
featuredPhoto | string | Optional | Primary photo URL (defaults to photos[0]) |
media | object | Optional | Media enhancement object (videos). Do not set media.photos. |
isFeatured | boolean | Optional | Featured listing flag Example: false |
engineType | string|number | Optional | Engine count (1-6); may also be inferred from engineN fields Example: 2 |
engineCount | number | Optional | Optional engine count (1-6); engineType is also accepted Example: 2 |
engine{1..6}Type | string | Optional | Engine type (required when publishing for each engine) Example: Inboard |
engine{1..6}Make | string | Optional | Engine make (required when publishing for each engine) Example: MAN |
engine{1..6}Model | string | Optional | Engine model (required when publishing for each engine) Example: V12 |
engine{1..6}Power | number | Optional | Engine power (required when publishing for each engine) Example: 1550 |
engine{1..6}FuelType | string | Optional | Engine fuel type Example: Diesel |
engine{1..6}FuelTank | number | Optional | Engine fuel tank (gal) Example: 0 |
engine{1..6}Hours | number | Optional | Engine hours Example: 0 |
engine{1..6}Year | number | Optional | Engine year Example: 0 |
{
"success": true,
"id": "YOUR_NEW_LISTING_ID",
"message": "Listing created successfully"
}To validate your payload without creating a listing, append ?dryRun=true (or set header x-dry-run: true). The response will include normalizedData and ignoredFields.
This is a complete, copy/paste-ready broker listing payload template (no auctions / no reserve / no Voyage fields).
{
"listingStatus": "active",
"condition": "Used",
"year": 2018,
"manufacturer": "Viking",
"model": "52 Sport Tower",
"type": "Sportfish",
"price": 1250000,
"contactForPrice": false,
"locationCity": "Fort Lauderdale",
"locationState": "FL",
"locationZIP": "33301",
"length": 52,
"beam": "16.5",
"draft": "4.5",
"cabins": "2",
"heads": "2",
"capacity": "β",
"cruiseSpeed": "β",
"maxSpeed": "β",
"holdingTank": "β",
"waterTank": "β",
"engineType": "2",
"engineCount": 2,
"engine1Type": "Inboard",
"engine1Make": "MAN",
"engine1Model": "V12",
"engine1Power": 1550,
"engine1FuelType": "Diesel",
"engine1FuelTank": 0,
"engine1Hours": 0,
"engine1Year": 0,
"engine2Type": "Inboard",
"engine2Make": "MAN",
"engine2Model": "V12",
"engine2Power": 1550,
"engine2FuelType": "Diesel",
"engine2FuelTank": 0,
"engine2Hours": 0,
"engine2Year": 0,
"description": "Well-maintained, captain-kept, and tournament-ready. Recent service, clean engine room, and updated electronics. Call for full specs and showing availability.",
"photos": [
"https://example.com/photo-1.jpg",
"https://example.com/photo-2.jpg"
],
"featuredPhoto": "https://example.com/photo-1.jpg",
"media": {
"videos": {
"longForm": [
"https://example.com/video-long-1.mp4"
],
"shortForm": [
"https://example.com/video-short-1.mp4"
]
}
},
"isFeatured": false
}/api/v1/update/:listingId
Updates one or more fields of an existing listing you own.
| Header | Value | Required |
|---|---|---|
x-api-key | Your broker API key (YOUR_BROKER_API_KEY_HERE) | Yes |
Content-Type | application/json | Yes |
| Parameter | Description |
|---|---|
listingId | The unique ID of the listing to update |
| Field | Type | Required | Description |
|---|---|---|---|
price | number | Optional | Updated price Example: 1199000 |
description | string | Optional | Updated description |
locationCity | string | Optional | Updated city |
locationState | string | Optional | Updated state |
photos | array | Optional | Replace photos array with new URLs |
{
"success": true,
"message": "Listing updated successfully",
"id": "YOUR_LISTING_ID"
}/api/v1/list?page=1&limit=25
Returns paginated active listings where brokerId matches your API key owner. (Alias: /api/v1/get)
| Header | Value | Required |
|---|---|---|
x-api-key | Your broker API key (YOUR_BROKER_API_KEY_HERE) | Yes |
| Parameter | Description |
|---|---|
page | Page number (default 1) |
limit | Items per page (default 25, max 100) |
{
"success": true,
"listings": [
{
"id": "YOUR_LISTING_ID",
"manufacturer": "Viking",
"model": "52 Sport Tower",
"year": 2018,
"price": 1250000,
"listingStatus": "active"
}
],
"pagination": {
"page": 1,
"limit": 25,
"total": 1,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}Quick start (fetch all active listings):
Use /api/v1/search with no filters. By default it returns active listings across SportFishTrader (scope: all). Use cursor pagination to pull the full catalog.
curl -X GET "https://www.sportfishtrader.com/api/v1/search?sort=createdat_desc&limit=25" \
+ -H "x-api-key: YOUR_BROKER_API_KEY_HERE" \
+ -H "x-api-version: v1"let cursor;
const all = [];
while (true) {
const url = new URL('https://www.sportfishtrader.com/api/v1/search');
url.searchParams.set('sort', 'createdat_desc');
url.searchParams.set('limit', '25');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url.toString(), {
headers: {
'x-api-key': 'YOUR_BROKER_API_KEY_HERE',
'x-api-version': 'v1',
},
});
const data = await res.json();
if (!res.ok) throw new Error(JSON.stringify(data));
all.push(...(data.listings ?? []));
cursor = data.pagination?.nextCursor;
if (!cursor) break;
}
console.log('Total listings:', all.length);Two common use cases:
scope=all to show SportFishTrader's active listings with optional filters.scope=mine to search only listings you own.The endpoint uses cursor pagination (no offsets). To fetch the next page, pass the returned pagination.nextCursor back as ?cursor=....
Keyword search: use q for fast prefix matching (e.g., q=viking 52). The backend converts your query into up to 10 tokens due to Firestore limits.
Example: /api/v1/search?scope=all&q=viking%2052&minPrice=500000&sort=createdat_desc&limit=25
Search gotchas:
q is an OR across tokens (Firestore array-contains-any), so q=viking 52 matches listings containing either token.FL are ok).q + filters + sort require a Firestore composite index. If you see INDEX_REQUIRED, remove filters or switch to sort=createdat_desc. After an index is deployed, it can take a few minutes to finish buildingβretry the request./api/v1/search
Search active listings (all listings) or search your own listings. Supports structured filters (manufacturer/model/type/price/year/etc) and cursor pagination.
| Header | Value | Required |
|---|---|---|
x-api-key | Your broker API key (YOUR_BROKER_API_KEY_HERE) | Yes |
| Parameter | Description |
|---|---|
scope | all | mine (default: all) |
listingStatus | Only when scope=mine. Default: active |
q | Keyword/prefix search across common listing fields (uses up to 10 tokens) |
manufacturer | Exact match filter |
model | Exact match filter |
type | Exact match filter (boat class/type) |
condition | Exact match filter (New|Used) |
engineType | Exact match filter |
locationState | Exact match filter (e.g., FL) |
locationCity | Exact match filter (e.g., Miami) |
year | Exact year match (overrides minYear/maxYear) |
minYear | Year range (inequality field; may override other ranges) |
maxYear | Year range (inequality field; may override other ranges) |
minPrice | Price range (inequality field; may override other ranges) |
maxPrice | Price range (inequality field; may override other ranges) |
minLength | Length range (inequality field; may override other ranges) |
maxLength | Length range (inequality field; may override other ranges) |
isFeatured | true|false |
contactForPrice | true|false |
includeDescription | true to include description HTML in results (default false) |
includeOwnerFields | Only when scope=mine. true to include brokerId/brokerageId/sellerId (default false) |
sort | createdat_desc|createdat_asc|price_asc|price_desc|year_asc|year_desc|length_asc|length_desc |
limit | 1..100 (default 25) |
cursor | Pagination cursor token from previous response |
{
"success": true,
"scope": "all",
"listingStatus": "active",
"sort": "createdat_desc",
"filters": {
"manufacturer": "Viking",
"minPrice": 500000,
"maxPrice": 1500000
},
"listings": [
{
"id": "LISTING_ID",
"listingStatus": "active",
"title": "2018 Viking 52 Sport Tower",
"manufacturer": "Viking",
"model": "52 Sport Tower",
"year": 2018,
"type": "Sportfish",
"price": 1250000,
"contactForPrice": false,
"locationCity": "Fort Lauderdale",
"locationState": "FL",
"locationZIP": "33301",
"length": 52,
"featuredPhoto": "https://...",
"photos": ["https://..."],
"isFeatured": false,
"createdAt": "2026-01-26T21:20:00.000Z",
"updatedAt": "2026-01-26T21:20:00.000Z"
}
],
"pagination": {
"limit": 25,
"count": 1,
"nextCursor": "BASE64URL_TOKEN_OR_NULL"
}
}/api/v1/sold/:listingId
Marks a listing you own as sold.
| Header | Value | Required |
|---|---|---|
x-api-key | Your broker API key (YOUR_BROKER_API_KEY_HERE) | Yes |
| Parameter | Description |
|---|---|
listingId | The listing ID to mark as sold |
{ "id": "YOUR_LISTING_ID" }Common errors you may encounter when using the Broker API:
Tip: Every response includes an X-Request-Id header. For debugging, log it and include it when contacting support.
Errors are returned as a structured JSON payload:
{
"code": "RATE_001",
"error": "Rate Limit Exceeded",
"message": "You have exceeded your API rate limit of 300 requests.",
"suggestion": "Your rate limit will reset at 2026-02-01T12:00:00.000Z. Consider implementing request queuing or caching to reduce API calls.",
"requestId": "d3c6c6b9-0b7c-4e12-a5a2-...",
"status": 429,
"endpoint": "createListingApi",
"method": "POST",
"timestamp": "2026-02-01T11:14:53.000Z"
}When rate limiting is enabled, responses also include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.
Error Code: AUTH_001
Error Message: x-api-key header is required
Cause: Request was sent without the x-api-key header
Solution:
Error Code: AUTH_002
Error Message: The provided API key does not match any active broker subscription.
Cause: Key is incorrect, terminated, or the subscription is not active
Solution:
Error Code: AUTH_005
Error Message: The API system is temporarily disabled by administrators.
Cause: Platform-level API setting is turned off
Solution:
Error Code: RATE_001
Error Message: You have exceeded your API rate limit.
Cause: Too many requests in the current rate limit window
Solution: