PHP
v0.2.0shadow-software/agt-php-sdk
composer require shadow-software/agt-php-sdk
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.
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 -sS https://americanguntrader.com/api/v1/dealer/me \
-H "Authorization: Bearer $AGT_API_KEY" \
-H "Accept: application/json"
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);
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$me = $agt->me();
echo $me['dealer_name'], PHP_EOL;
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.
Versions and download links come straight from each repository's GitHub releases, so this page can never advertise a build we stopped shipping.
shadow-software/agt-php-sdk
composer require shadow-software/agt-php-sdk
@shadow-software/agt-sdk
npm install @shadow-software/agt-sdk
The machine-readable document behind the dealer API — import it into Postman or Insomnia, or generate a client in any language.
AGT Sync publishes your WooCommerce catalog and keeps stock honest in both directions. No code — it connects over OAuth.
Two ways in. Pick by who holds the credential, not by which is newer.
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.
https://americanguntrader.com/.well-known/oauth-authorization-server/dealer
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.
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.
POST https://americanguntrader.com/mcp/dealer — Claude, Cursor, any MCP client.
POST https://americanguntrader.com/api/v1/dealer/tools/{tool} — the same handlers as MCP, over plain HTTP.
/api/v1/dealer/* — what the WooCommerce plugin speaks. Unchanged.
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.
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.
{
"mcpServers": {
"american-gun-trader": {
"type": "http",
"url": "https://americanguntrader.com/mcp/dealer",
"headers": {
"Authorization": "Bearer agt_key_your_key_here"
}
}
}
}
agt_key_* token for it.
Ready to connect? Mint a key under Settings → AI Assistant.
| 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 |
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.
Dealer identity, FFL status, and publish readiness.
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.
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 '{}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_profile', []);
Create, update, list, and restore marketplace listings.
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). |
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 '{}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_list', []);
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). |
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"
}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_show', [
'url_slug' => 'string',
]);
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). |
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"
}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_status', [
'slugs' => 'string',
]);
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). |
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"
}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_reviews', [
'url_slug' => 'string',
]);
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. |
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
}'
// 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();
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,
]);
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). |
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"
}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_update', [
'url_slug' => 'string',
]);
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). |
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"
}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_delete', [
'url_slug' => 'string',
]);
listings:write
Restore a soft-deleted listing belonging to the authenticated dealer.
| Parameter | Type | Required | Description |
|---|---|---|---|
url_slug |
string | yes | Listing UUID (url_slug). |
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"
}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_listings_restore', [
'url_slug' => 'string',
]);
Categories, manufacturers, calibers, applications, and conditions.
taxonomy:read
Read the category tree, manufacturers, calibers, applications, and conditions required to build a valid listing.
Takes no parameters.
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 '{}'
// 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();
use ShadowSoftware\Agt\AgtClient;
$agt = new AgtClient(apiKey: getenv('AGT_API_KEY'));
$result = $agt->tool('dealer_taxonomy', []);
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. |