Skip to main content

Dealer API & AI Assistant

Verified FFL dealers get a versioned REST API, long-lived API keys, and an MCP server — so Claude, Cursor, a WooCommerce store or your own scripts can run your inventory. Listing quantity sync is separately entitled and requires the listings:inventory scope.

  • REST · MCP · tool facade
  • 120 req/min per dealer
  • Scoped, revocable keys
  • No per-call fees

Your first request

Mint an API key under Settings → AI Assistant, export it as AGT_API_KEY, and call /api/v1/dealer/me. A verified FFL with an active vendor subscription gets a 200 with its dealer profile.

curl
curl -sS https://americanguntrader.com/api/v1/dealer/me \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Accept: application/json"
TypeScript
import { createAgtDealerClient } from '@shadow-software/agt-sdk';

const agt = createAgtDealerClient({ apiKey: process.env.AGT_API_KEY! });

const { data, error, response } = await agt.GET('/me');
if (!response.ok || !data) {
  throw new Error(`Dealer API request failed (${response.status})`, { cause: error });
}
console.log(data.data.business_name ?? data.data.username, data.data.ffl_verified);
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$me = $agt->me();
echo $me['dealer_name'], PHP_EOL;

Official sandbox

SDKs, WordPress reviewers, and automated smoke tests may use the synthetic Shadow sandbox. Credentials are issued privately; keep AGT_SANDBOX_API_KEY in your secret store, never in source control. The account is reconciled hourly without rotating its long-lived key.

See the repository docs/official-sandbox.md guide for local setup.

SDKs & downloads

Versions and download links come straight from each repository's GitHub releases, so this page can never advertise a build we stopped shipping.

OpenAPI spec

The machine-readable document behind the dealer API — import it into Postman or Insomnia, or generate a client in any language.

WooCommerce plugin

v1.0.3

AGT Sync publishes your WooCommerce catalog and keeps stock honest in both directions. No code — it connects over OAuth.

Authentication

Two ways in. Pick by who holds the credential, not by which is newer.

OAuth 2.0 (dynamic registration + PKCE)

Store plugins — this is what AGT Sync for WooCommerce uses

The client registers itself (RFC 7591), sends the dealer to a consent screen, and exchanges an authorization code with PKCE. The dealer never copies a secret into a third-party app, and can revoke the connection without rotating anything else.

  1. POST /oauth/dealer/register — the client registers itself and receives a client_id
  2. Redirect the dealer to /oauth/dealer/authorize with a PKCE challenge and the scopes you need
  3. Exchange the returned code at /oauth/dealer/token for access + refresh tokens
  4. Revoke at /oauth/dealer/revoke (RFC 7009) when the dealer disconnects

https://americanguntrader.com/.well-known/oauth-authorization-server/dealer

API key

Your own scripts, back-office jobs, and AI assistants

The dealer mints a long-lived agt_key_* token in their own account and pastes it into software they control. Simplest path when there is no third party to protect the credential from.

  1. Mint a key under Settings → AI Assistant
  2. Send it as Authorization: Bearer agt_key_… on REST, the tool facade, and MCP alike
  3. Revoke it from the same screen; revocation takes effect on the next request

https://americanguntrader.com/api/v1/dealer/me

Every request re-checks that your FFL is verified and your vendor subscription is active — revoking either takes effect on the very next call, not at the end of a billing period.

Send Authorization: Bearer agt_key_… on both MCP and REST calls.

Transports

MCP

POST https://americanguntrader.com/mcp/dealer — Claude, Cursor, any MCP client.

Tool facade

POST https://americanguntrader.com/api/v1/dealer/tools/{tool} — the same handlers as MCP, over plain HTTP.

Legacy REST

/api/v1/dealer/* — what the WooCommerce plugin speaks. Unchanged.

Rate limits

120 requests/minute and 5,000/day, counted per dealer — not per IP, so a neighbour on shared hosting cannot throttle you. Either ceiling returns 429.

image_urls must be https:// and are SSRF-guarded — private and link-local hosts are rejected, and redirects are re-checked at every hop. Create also accepts an Idempotency-Key header or an idempotency_key body field, so a retried publish cannot double-list a gun.

Connect an AI assistant

Every tool below is an MCP tool. Point an MCP client at https://americanguntrader.com/mcp/dealer and it can list, publish, update and retire your inventory conversationally — no scraping, no CSV round-trip. Scopes apply exactly as they do over REST, so a read-only key stays read-only in the assistant.

MCP client config
{
  "mcpServers": {
    "american-gun-trader": {
      "type": "http",
      "url": "https://americanguntrader.com/mcp/dealer",
      "headers": {
        "Authorization": "Bearer agt_key_your_key_here"
      }
    }
  }
}
The AI Assistant screen in AGT settings, showing the MCP endpoint field with a Copy button and the API token creation form
Settings → AI Assistant: copy your MCP endpoint, then mint an agt_key_* token for it.

Ready to connect? Mint a key under Settings → AI Assistant.

Scopes

Scope Meaning
listings:read See your listings and their status, views and bids
listings:write Publish, update and remove listings on your behalf
listings:inventory Read and update synced listing quantities and stock status
taxonomy:read Read the category, manufacturer and caliber lists
profile:read See your dealer name, FFL status and account limits

Tool reference

Every tool is available on MCP and at POST /api/v1/dealer/tools/{name}. Parameters and examples are read from each tool's own schema — the same definition the MCP transport validates against — so this reference cannot describe arguments the endpoint does not accept.

Profile

Dealer identity, FFL status, and publish readiness.

dealer_profile read requires profile:read

Identify the connected FFL dealer — username, business name, FFL status, publish readiness, blockers, and listing field limits. Use to validate an API token during setup.

Takes no parameters.

Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_profile \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_profile', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_profile', []);

Listings

Create, update, list, and restore marketplace listings.

dealer_listings_list read requires listings:read

List the authenticated dealer's marketplace listings, newest first. Supports updated_since, limit, and offset.

Parameter Type Required Description
updated_since string no ISO-8601 timestamp — only return listings updated at or after this time.
limit integer no Page size (1–100, default 50).
offset integer no Offset for pagination (default 0).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_list \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_list', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_list', []);
dealer_listings_show read requires listings:read

Fetch one of the authenticated dealer's listings by its public url_slug (UUID).

Parameter Type Required Description
url_slug string yes Listing UUID (url_slug).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_show \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_slug": "string"
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_show', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "url_slug": "string"
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_show', [
    'url_slug' => 'string',
]);
dealer_listings_status read requires listings:read

Bulk status poll for known listing slugs — status, sold_at, moderation, views, bid_count, and public URL. At most 200 slugs per call.

Parameter Type Required Description
slugs string yes Comma-separated listing UUIDs (url_slug values).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_status \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slugs": "string"
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_status', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "slugs": "string"
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_status', [
    'slugs' => 'string',
]);
dealer_listings_reviews read requires listings:read

Read the authenticated dealer listing review contract. Returns public dealer buyer ratings, summary, pagination, and stale behavior for deleted or unpublished listings.

Parameter Type Required Description
url_slug string yes Listing UUID (url_slug).
limit integer no Page size (1–100, default 20).
offset integer no Offset for pagination (default 0).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_reviews \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_slug": "string"
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_reviews', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "url_slug": "string"
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_reviews', [
    'url_slug' => 'string',
]);
dealer_listings_create write requires listings:write

Create a new marketplace listing for the authenticated dealer. Requires title, description, price, condition, category_id, and at least one image (multipart images on the REST facade, or https image_urls for MCP).

Parameter Type Required Description
title string yes Listing title (plain text, max 80).
description string yes Listing description (min 80 plain characters).
price number yes Price in USD.
condition integer yes 1 New, 2 Used, 3 Like New, 4 Damaged.
category_id integer yes Category id from dealer_taxonomy.
manufacturer_id integer no Optional manufacturer id from dealer_taxonomy.
caliber_id integer no Optional caliber id from dealer_taxonomy.
weight number no Weight in pounds.
applications array no Application ids.
image_urls array no HTTPS image URLs when not uploading multipart files (SSRF-guarded).
idempotency_key string no Optional idempotency key (max 64). Also accepted as Idempotency-Key header.
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_create \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "string",
    "description": "string",
    "price": 1,
    "condition": 1,
    "category_id": 1
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_create', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "title": "string",
    "description": "string",
    "price": 1,
    "condition": 1,
    "category_id": 1
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_create', [
    'title' => 'string',
    'description' => 'string',
    'price' => 1,
    'condition' => 1,
    'category_id' => 1,
]);
dealer_listings_update write requires listings:write

Update one of the authenticated dealer's listings by url_slug. Only supplied fields are changed. Optional images / https image_urls replace the gallery when provided.

Parameter Type Required Description
url_slug string yes Listing UUID (url_slug).
title string no Listing title.
description string no Listing description.
price number no Price in USD.
condition integer no 1 New, 2 Used, 3 Like New, 4 Damaged.
category_id integer no Category id.
manufacturer_id integer no Manufacturer id.
caliber_id integer no Caliber id.
weight number no Weight in pounds.
inventory_quantity integer no Synced quantity (requires an entitled FFL dealer and listings:inventory).
applications array no Application ids (replaces the set when provided).
image_urls array no HTTPS image URLs when not uploading multipart files (SSRF-guarded).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_update \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_slug": "string"
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_update', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "url_slug": "string"
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_update', [
    'url_slug' => 'string',
]);
dealer_listings_delete write requires listings:write

Soft-delete one of the authenticated dealer's listings by url_slug. Reversible via dealer_listings_restore.

Parameter Type Required Description
url_slug string yes Listing UUID (url_slug).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_delete \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_slug": "string"
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_delete', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "url_slug": "string"
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_delete', [
    'url_slug' => 'string',
]);
dealer_listings_restore write requires listings:write

Restore a soft-deleted listing belonging to the authenticated dealer.

Parameter Type Required Description
url_slug string yes Listing UUID (url_slug).
Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_restore \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url_slug": "string"
}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_listings_restore', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({
    "url_slug": "string"
}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_listings_restore', [
    'url_slug' => 'string',
]);

Taxonomy

Categories, manufacturers, calibers, applications, and conditions.

dealer_taxonomy read requires taxonomy:read

Read the category tree, manufacturers, calibers, applications, and conditions required to build a valid listing.

Takes no parameters.

Example request
curl
curl -sS -X POST https://americanguntrader.com/api/v1/dealer/tools/dealer_taxonomy \
  -H "Authorization: Bearer $AGT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
TypeScript
// Tool invocation uses HTTP; it is outside the TypeScript SDK schema.
const response = await fetch('https://americanguntrader.com/api/v1/dealer/tools/dealer_taxonomy', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AGT_API_KEY}`,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify({}),
});
if (!response.ok) {
  throw new Error(`Dealer API request failed (${response.status})`);
}
const result = await response.json();
PHP
use ShadowSoftware\Agt\AgtClient;

$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));

$result = $agt->tool('dealer_taxonomy', []);

Errors

Every failure returns JSON with an error code and a human message. Branch on the code, not the message.

Status Code Meaning What to do
401 authentication_required No bearer token was sent. Send Authorization: Bearer with an API key or OAuth access token.
401 invalid_token The token is unknown, expired, or revoked. Refresh the OAuth token, or mint a new API key. Both return a WWW-Authenticate header naming the error.
403 dealer_inactive The FFL is not verified, or the vendor subscription has lapsed. This is re-checked on EVERY request, so a lapsed subscription stops publishing at once. Restore the subscription; the same token then works.
403 insufficient_scope The token is valid but lacks the scope this tool requires. Re-authorize with the missing scope; the response names it in the WWW-Authenticate header.
404 unknown_tool No tool by that name. Check the tool list on this page or in spec.json — names are stable.
422 validation_error Arguments failed validation, or the tool refused the request. The body names the offending field. image_urls must be https and are SSRF-guarded.
429 rate_limited Burst or daily ceiling exceeded for this dealer. Back off using the Retry-After header. Limits are per dealer, not per IP.