The API reads and writes the same rows as the In-house inventory screen: in wp-admin, FFLMonkey.com › Data Feed › In-house inventory, or, for a store on its own website, the licence's settings in your FFL Monkey account. Each row is one product you hold yourself, identified by its UPC, with a stock count and the price you sell it at. Anything you change here shows up on that screen, and anything changed there shows up here.
It competes with your distributors' offers for the same product. The cheapest offer wins, and at an equal price your in-house row wins.
Your price is published as you set it. Your pricing rules and default margin do not apply to in-house rows.
Your restrictions still apply. A rule about the product (its category, brand, product class or UPC, or your own SKU) or about stock hides your in-house row the same way it hides a distributor's offer. Rules about a distributor or a cost range do not apply to items you own.
A row with stock 0 is kept but not offered.
cost_price is for your own records. It does not change what your store shows.
A row only reaches your store when our catalog carries its UPC
Every item response says so in in_catalog (and product_id). When in_catalog is false, the row is saved and returned like any other, but there is no catalog product for it to attach to, so it does not appear in your store. It starts competing on its own as soon as a distributor supplies that UPC and the product is added to the catalog. You do not need to send it again.
If you have paused updates to your store in your FFL Monkey settings, changes you make through the API are still saved, but your store does not receive them while updates are paused. When you turn updates back on, your store is brought up to date.
Authentication
Send the key in the Authorization header of every request:
Authorization: Bearer ffl_sk_live_3f9a…
Keys start with ffl_sk_live_ followed by 49 letters and digits. A key belongs to one store and can only see and change that store's rows. Reading needs inventory:read, which a key given In-house inventory has; changing anything needs inventory:write, which only a key given In-house inventory: Read and write has. Create a key.
Keep the key on a server
It is a secret that can change your store's stock and prices. Use it from your own server or a script, and never in a web page or browser code. The API does not answer browsers' cross-origin requests, on purpose.
A missing, mistyped, unknown or revoked key gets 401 invalid_api_key. If your FFL Monkey subscription is suspended, cancelled or not yet active, every request but GET /key gets 403 license_not_active, with the reason. GET /key still answers, and reports the same in license.
Base URL and formats
https://backend.fflmonkey.com/api/v1/inventory
Send request bodies as JSON with Content-Type: application/json. Responses are always JSON.
Times are ISO 8601, in UTC: 2026-10-07T15:00:00+00:00.
Money comes back as a string with two decimals ("499.00"). You may send it as a number or a string, with at most two decimals.
A UPC is 1 to 32 letters, digits or hyphens, exactly as you store it. Leading zeros matter: 012345678905 and 12345678905 are different rows.
Rate limit
Each key may make 120 requests a minute, counted across the whole API: its Store API requests count towards the same 120. A batch request counts as one request however many rows it carries, so use the batch endpoint for anything over a handful of rows.
Every response to a valid key carries X-RateLimit-Limit and X-RateLimit-Remaining, refusals included: a 403 counts like any other request. A 404 or 405 for an address or a method the API does not have is answered before it is counted, and carries neither header. Past the limit you get 429 rate_limited with a Retry-After header giving the seconds to wait.
Errors
Every error has the same shape. Branch on error; show message to a person. New codes may be added, so treat an unknown error by its HTTP status.
0 or more. A row with stock 0 is kept but not offered.
price
string
Two decimals, such as "499.00". Published as you set it.
cost_price
string or null
Two decimals. For your own records: it does not change what your store shows.
notes
string or null
Your notes.
source
string
How the row was last written: manual, csv, spreadsheet (the In-house inventory screen) or api. Every write through this API sets it to api. More values may be added.
in_catalog
boolean
Whether our catalog carries a product with this UPC. false: saved, but it cannot reach your store until it does.
product_id
integer or null
The catalog product's id, or null when in_catalog is false.
created_at
string
ISO 8601, UTC.
updated_at
string
ISO 8601, UTC.
Endpoints
Every endpoint has a page with its fields and a documented example.
This is version 1, and the /v1/ in the address is a promise: it only grows. We may add endpoints, response fields, optional request fields, error codes and values (such as a new source), so ignore fields you do not know and treat unknown values as valid.
We will not remove or rename a field, change its type or meaning, or make an optional field required. A change that would break that is a new version at a new address.
OpenAPI document
A machine-readable description of this API (OpenAPI 3.1), for Postman, code generators and similar tools: