Dealer documentation
Dealer API & inventory feeds
Yes, we have an API. It lets a Texas Gun Finder dealer keep their inventory on this site in sync with their own system — publish new items, update prices, and pull sold items down, without anyone retyping anything into a dashboard.
It is a write API. It exists so your inventory reaches Texas buyers accurately and stays current. There is no public endpoint for querying our index.
This is a technical document.
It is written for whoever maintains your website, inventory system, or point-of-sale. If that is not you, forward this page — most e-commerce and POS platforms can do one of the two integrations below with a modest amount of work.
Not a Texas Gun Finder dealer yet? Start with the dealer application form. We provision your account, then you generate a key from your dashboard.
Push or pull — which should I use?
Two integrations, same destination. Pick one; you do not need both.
Option 1
Push — you POST to us
Your system sends us JSON whenever inventory changes. Updates appear within seconds.
Choose this if you have a developer or a POS vendor who can fire an HTTP request on an inventory event, and you want price changes live immediately.
Option 2
Pull — we fetch your file
You publish a CSV or JSON file at a URL. We poll it every six hours.
Choose this if your system can already export a Google Merchant Center feed, or you would rather drop a file on a cron job than write an integration. No API key needed.
Use your own SKU as the item ID, and keep it stable.
This is the single most important thing on this page, and it applies to both integrations.
Every item you send carries an id that you choose — use
your existing SKU or product ID, and send the same ID for the same item every time.
When the ID and title are unchanged, we recognize the item and skip re-processing it entirely. Re-sending your whole catalog costs nothing and changes nothing. If the ID changes between submissions we cannot tell an update from a new item: you get a duplicate listing, the old one goes stale, and your inventory count drifts. Randomly generated or row-number IDs are the usual cause.
Getting an API key
Push only. The pull/feed integration does not use a key.
- Log in and open your dealer dashboard.
- Under API access, click Generate key and give it a name you will recognize later, e.g. the name of your POS.
- Copy the key immediately.
The key is shown once and never again.
We store only a hash of it, so we genuinely cannot recover it for you — not from a backup, not by asking support. Paste it straight into your integration's secret store. If you lose it, revoke it from the dashboard and generate a new one.
Treat it like a password: it can write and delete your listings. Do not put it in client-side JavaScript, a public repository, or a URL. Revoke it from the dashboard the moment you suspect it leaked — revocation takes effect on the next request. You can hold several keys at once, so you can bring a replacement up before retiring the old one.
Sending the key
Every push request carries it in the Authorization header:
Authorization: Bearer tgf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
We do not accept the key as a query parameter. Query strings end up in access logs and browser history, which is a poor place for a credential that can delete your inventory.
Check that it works
Before writing anything, confirm your key resolves to the account you expect:
curl https://texasgunfinder.com/api/v1/whoami \
-H "Authorization: Bearer $TGF_API_KEY"
{
"dealer": {
"slug": "clarks-sporting",
"business_name": "Clarks Sporting Goods",
"status": "active",
"storefront_url": "https://texasgunfinder.com/d/clarks-sporting/",
"source_key": "dealer-clarks-sporting"
},
"api_key": {"prefix": "tgf_live_8fa2c1d4", "name": "Lightspeed POS", "created_at": "2026-08-16T14:02:11+00:00"},
"limits": {"max_items_per_request": 100, "max_body_bytes": 2000000}
}
Push API
One endpoint, three methods. Your key determines which dealer the write applies to — there is no dealer identifier in the request body, and a key can only ever touch its own dealer's listings.
POST https://texasgunfinder.com/api/v1/listings- Create or update up to 100 listings.
DELETE https://texasgunfinder.com/api/v1/listings- Take listings down by ID (sold, pulled from the floor).
GET https://texasgunfinder.com/api/v1/listings- Read back what we currently hold for you, to reconcile against your own system.
Creating and updating
Send a batch. There is no separate create and update call — we match on your
id, so the same request creates the item the first time
and updates it every time after.
curl -X POST https://texasgunfinder.com/api/v1/listings \
-H "Authorization: Bearer $TGF_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"id": "SKU-001",
"title": "Sig Sauer P320 XCARRY 9mm 4.6\" 17rd",
"price_cents": 74999,
"category_slug": "handguns",
"city_slug": "fort-worth",
"condition": "new",
"brand": "Sig Sauer",
"model": "P320",
"caliber": "9mm",
"capacity": "17",
"barrel_length": "4.6",
"zip": "76102",
"url": "https://clarkssporting.com/inventory/12345",
"description": "New in box. X-Series slide, Romeo1 Pro optic plate.",
"image_urls": [
"https://clarkssporting.com/img/12345/main.jpg",
"https://clarkssporting.com/img/12345/side.jpg"
],
"available": true
},
{
"id": "SKU-002",
"title": "Ruger 10/22 Carbine",
"price_cents": 34900,
"category_slug": "rifles",
"city_slug": "austin",
"condition": "used"
}
]
}'
Response — 200 OK when every item landed:
{
"received": 2,
"succeeded": 2,
"failed": 0,
"results": [
{"index": 0, "id": "SKU-001", "listing_id": 918233, "status": "created", "active": true,
"url": "https://texasgunfinder.com/go/918233/"},
{"index": 1, "id": "SKU-002", "listing_id": 918234, "status": "updated", "active": true,
"url": "https://texasgunfinder.com/go/918234/"}
],
"errors": []
}
Partial success
One bad item does not reject the rest of the batch. When some items fail you get
207 Multi-Status, the good items are already saved, and
every failure names the item and the field so you know exactly what to fix:
{
"received": 3,
"succeeded": 2,
"failed": 1,
"results": [ ... ],
"errors": [
{
"index": 2,
"id": "SKU-003",
"field": "price_cents",
"error": "validation_failed",
"message": "must be an integer number of cents, got 749.99. Send 74999 for $749.99 — not 749.99."
}
]
}
Fix the reported items and re-send just those, or re-send the whole batch — the items that already succeeded are matched by ID and cost nothing to repeat.
Removing sold items
Stale inventory is worse than no inventory: a buyer who drives across town for a rifle you sold last week does not come back. Take items down as they sell.
curl -X DELETE https://texasgunfinder.com/api/v1/listings \
-H "Authorization: Bearer $TGF_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ids": ["SKU-001", "SKU-002"]}'
{"requested": 2, "deactivated": 2, "not_found": 0,
"results": [{"id": "SKU-001", "listing_id": 918233}, {"id": "SKU-002", "listing_id": 918234}],
"not_found_ids": []}
This unpublishes the listing rather than erasing it, so re-sending the same ID later brings the
item back with its history intact. Deleting an ID we do not have is reported in
not_found_ids rather than failing the request, so a retry
is always safe. You can also set "available": false on a
normal POST for the same effect.
Reconciling
curl "https://texasgunfinder.com/api/v1/listings?active=true&limit=200&offset=0" \
-H "Authorization: Bearer $TGF_API_KEY"
Returns your listings with the id you supplied, our
listing_id, and whether each is currently published.
Use it to find items that silently failed to go live. Optional parameters:
active (true /
false), limit
(max 200), offset.
Field reference
Applies to the push API. The equivalent columns for CSV and JSON feeds are in the feed spec below.
Required
id— string. Your stable SKU or product ID. The upsert key; see the note at the top of this page.title— string, max 200 characters. What buyers search. Include brand, model, and a key spec.price_cents— integer US cents.74999means $749.99. Not a decimal, not a string. We reject floats rather than guess, because a dealer sending749.99meaning cents would publish a $7.50 rifle. Must be at least 1 cent and at most $100000. Call-for-price items are not accepted — omit them.category_slug— one of the values listed below.city_slug— the Texas city the item is physically in, lowercase and hyphenated:fort-worth,san-antonio. Usually your shop's city.
Optional
condition— one ofnew,like_new,used,refurbished,needs_work. Omitting it means the item is treated as new, which is the right default for retail stock but wrong for a consignment gun — set it explicitly on used inventory.available— boolean, defaulttrue.falsestores the item without publishing it.url— the item's page on your own site. Buyers are sent here. If you omit it we link to a Texas Gun Finder page for the item instead.description— plain text, max 8000 characters.image_urls— array of absolute http(s) URLs, up to 8. First is the primary. We mirror images to our own CDN on ingest, so hotlink protection on your origin will not break display.zip— US ZIP. Improves distance-based search.brand,model,caliber,capacity,barrel_length,gtin— manufacturer and spec metadata used for search and filtering. Strings, max 100 characters each.
Fields we do not recognize are ignored rather than rejected, so a typo in an optional field name fails silently. If an attribute is not showing up on your listings, check the spelling against this list.
Category slugs
Current values — this list is generated live, so it is always what the API will accept:
airguns · ammunition · archery · blades · cases · firearms · handguns · holsters · hunting_gear · nfa · optics · other · parts · reloading · rifles · services · shotguns · tactical_gear
Limits
- Up to 100 items per request. A 500-item catalog is 5 calls.
- 60 requests per 60 seconds per key. Batch rather than sending items one at a time and you will not come near this.
- Request body up to 2MB.
- Up to 8 images per listing.
Error codes
Every error response has the same shape: a stable
error string to branch on, and a
message for your logs. Per-item errors additionally carry
index, id, and
field.
400 invalid_json/invalid_payload/empty_body- The body could not be parsed, or the
itemsarray is missing, empty, or over the batch limit. The message names the line and column for a JSON syntax error. 401 missing_credentials- No
Authorization: Bearerheader. Note it isBearer, notBasicorToken. 401 invalid_credentials- The key is unrecognized or has been revoked. Check for a truncated paste, then generate a fresh key from the dashboard.
403 dealer_suspended- The account is suspended and cannot write. Contact us.
405- Method not allowed on this endpoint.
/listingsaccepts GET, POST, and DELETE only. 413 payload_too_large- Body over 2MB. Send smaller batches.
429 rate_limited- Over the request-rate limit. Honor the
Retry-Afterheader, and batch your items. - Per-item
validation_failed - A field on that item is missing, the wrong type, or not an accepted value. The
fieldandmessagesay which and why. The rest of the batch still saved. - Per-item
rejected - The item was well-formed but our pipeline declined it — most often a zero or absent price.
- Per-item
ingest_failed - Something broke on our side while saving that item. The rest of the batch is unaffected; retry the item, and tell us if it persists.
A successful item can still come back with "active": false
and a note. That means we stored it but will not publish
it — either you sent available: false, or the item did not
clear the indexing gate, which requires a resolvable category and a real price.
Inventory feed (pull)
The alternative to the push API: you publish a file at a URL, we fetch it on a schedule. No API key, no code — if your system already exports a Google Merchant Center feed you are most of the way there. Set the URL under Inventory feed in your dealer dashboard, where you can also preview exactly how we parse it before saving.
Two formats, same resulting listings. Choose whichever your tooling produces more naturally.
CSV format
UTF-8, comma-separated, header row on the first line. Column names follow the Google Merchant Center specification wherever an equivalent field exists; the firearms-specific columns extend it.
id,title,link,price,condition,availability,brand,mpn,image_link,additional_image_link,description,category_slug,city_slug,caliber,capacity,barrel_length,zip
SKU-001,"Sig Sauer P320 XCARRY 9mm 4.6"" 17rd",https://clarkssporting.com/inventory/12345,749.99 USD,new,in_stock,Sig Sauer,P320,https://clarkssporting.com/img/12345/main.jpg,https://clarkssporting.com/img/12345/side.jpg;https://clarkssporting.com/img/12345/back.jpg,New in box; X-Series slide,handguns,fort-worth,9mm,17,4.6,76102
SKU-002,Ruger 10/22 Carbine,https://clarkssporting.com/inventory/12346,349.00 USD,used,in_stock,Ruger,10/22,https://clarkssporting.com/img/12346.jpg,,Rimfire classic,rifles,austin,.22 LR,10,18.5,78701
Required columns:
id— your stable SKU. Upsert key, same rules as the push API.title— listing title.link— the item's public URL on your site.price— GMC format:749.99 USD, a number then a space then the currency. Note this differs from the push API, which takes integer cents. Non-USD rows are skipped.category_slug— see the category list.city_slug— lowercase, hyphenated Texas city.
Optional columns:
condition—new,like_new,used,refurbished,needs_work.availability—in_stockorout_of_stock. Out-of-stock rows are ingested but left unpublished.description— plain text.image_link— primary image URL.additional_image_link— further image URLs, semicolon-separated per the GMC spec. Up to 8 images total.brand,mpn,gtin— manufacturer identifiers.mpnmaps to the model field.caliber,capacity,barrel_length,zip— spec metadata.
JSON Feed 1.1 format
A standard JSON Feed 1.1
document. Firearms-specific fields go in a _tgf object on
each item, following the JSON Feed extension convention.
{
"version": "https://jsonfeed.org/version/1.1",
"title": "Clarks Sporting — Inventory",
"home_page_url": "https://clarkssporting.com/",
"items": [
{
"id": "SKU-001",
"url": "https://clarkssporting.com/inventory/12345",
"title": "Sig Sauer P320 XCARRY 9mm 4.6\" 17rd",
"content_text": "New in box, X-Series slide, Romeo1 Pro plate.",
"image": "https://clarkssporting.com/img/12345/main.jpg",
"attachments": [
{"url": "https://clarkssporting.com/img/12345/side.jpg", "mime_type": "image/jpeg"}
],
"_tgf": {
"price_cents": 74999,
"category_slug": "handguns",
"city_slug": "fort-worth",
"zip": "76102",
"brand": "Sig Sauer",
"mpn": "P320",
"caliber": "9mm",
"capacity": "17",
"barrel_length": "4.6",
"condition": "new",
"availability": "in_stock"
}
}
]
}
- Required on the item:
id,title,url. - Required under
_tgf:price_cents(integer cents, as in the push API — not dollars),category_slug,city_slug. - Everything else is optional and matches the CSV column list above.
Polling behavior
- User agent
- We fetch with
TexasGunFinderBot/1.0 (+https://texasgunfinder.com/api/). Please allow it in any WAF, bot-management, orrobots.txtrule covering the feed URL. - Interval
- At most one fetch per feed per six hours. Our scheduler checks hourly for feeds that are due. If you need faster than six hours, use the push API.
- Format detection
- From the
Content-Typeresponse header (text/csvorapplication/json), falling back to inspecting the first byte of the body. Serving the right Content-Type avoids any ambiguity. - Deletions
- Absence is a delete. Any item in a previous poll that is missing from the current one is unpublished. Include your entire active catalog in every feed — a partial feed will take everything you left out offline. Re-adding an item republishes it with its history intact.
- Failure handling
- A fetch or parse failure applies no changes at all — your existing listings are left exactly as they were, so a briefly broken feed cannot wipe your inventory. We retry with a widening backoff; ten consecutive failures disable the feed and email your contact on file.
- Row errors
- Individual rows that fail validation are skipped and reported in the feed status panel on your dashboard. The rest of the feed still ingests. The common causes are an unrecognized
category_slugorcity_slug, and a price that is not in749.99 USDform.
Support
Integration questions: [email protected].
Include your dealer slug and, for a push problem, the key prefix (the
tgf_live_… part shown in your dashboard) — never the key itself.
Not a dealer yet? Apply here and we will get you set up.