PriceZag API — Connect pricing data to your systems

Use the PriceZag JSON API to read competitor prices and stock, manage products and repricing rules, download reports, and automate your workflows.

Quick curl example

We know developers like to see working code first. These commands create a test product and then retrieve it. Replace MYAPIKEY with your account key (see API key).
Create a test product:
curl -vX POST 'https://pricezag.com/api/v3/MYAPIKEY' -d '{"actions":[{"a":"productCreate","code":"000-SOME-TEST-PRODUCT","name":"Some Test Product created via PriceZag API","price":12.34,"currency":"USD"}]}'
(execute the command a second time and you will get an error because the product already exists)
Now you can read that product back:
curl -X POST 'https://pricezag.com/api/v3/MYAPIKEY' -d '{"actions":[{"a":"productGet","code":"000-SOME-TEST-PRODUCT"}]}' | python -mjson.tool
(remove | python -mjson.tool if you don't have that tool which parses the JSON response in your terminal)

Overview

What you can do with the PriceZag API

The PriceZag JSON API lets your scripts and business systems work directly with the competitor prices and stock we monitor. Read product data, manage products and categories, calculate amounts from repricing rules, configure alerts, and download custom spreadsheets. You can use the API with any ecommerce platform or programming language capable of sending HTTPS requests and processing JSON.

You can also ask your favorite AI assistant to write the scripts for you. Give it the URL of this documentation (https://pricezag.com/integrations/api), explain what you want to automate, and tell it which programming language or ecommerce platform you use. Keep your API key private - do not paste it into an AI conversation.

PriceZag handles monitoring inside our platform, so the API is optional. It's useful when you want to connect your own system to that data. Shopify and WooCommerce also have direct integrations.

API key and permissions

In your PriceZag account, open Automation > API. Click Reveal the current API key to view your existing key, or Change the API key to replace it. Both operations ask for your account password. Your user must have the relevant API-key permissions (viewing or generating keys). A user without permission to view or generate keys must ask an account administrator.

Keep the API key secret. It can allow extensive changes to account data, including deletion. Do not put the key in public repositories, browser-side code, or AI conversations. If you need someone to help build an integration, give them a PriceZag user with the necessary permissions and rotate the API key when appropriate.

API access depends on your membership. With full API access, your scripts can work with all your products. Without it, the API can access only the first 10 products sorted alphanumerically by reference code. For example, if you have 1,000 products with codes from SKU0001 to SKU1000, the API can access SKU0001 through SKU0010. The initial full-feature period of the Free Forever plan includes full API access. You can also enable full API access as an option in your membership.

API activity log and last request

The Automation > API page in your account shows the time of the last API request and an API Activity Log button. Check these when a script is not working: you can see whether requests reached PriceZag and investigate failed calls or unexpected changes.

Changes made through the API also appear in the History of Edits throughout your account, under the user name API. For example, a product's history might show Price edited by API instead of Price edited by JOHN. PriceZag supports multiple users in the same account, so you can distinguish automated changes from those made by individual team members.

Request URL and methods

All API requests use the same HTTPS URL. Send POST requests to it, replacing MYAPIKEY with your key:

https://pricezag.com/api/v3/MYAPIKEY

What you do with the API (the "actions") and their parameters belong in the JSON request body, not in the URL.
Send a JSON object containing one or more actions in the POST body. The URL stays exactly the same whether you read a product, update prices, or download a spreadsheet.

Cannot send POST?
As a last resort, you can send a GET request with the JSON body encoded as base64 in the ?data= URL parameter. URLs have length limits, so POST is strongly preferred.

How actions work

What we call an action is something you do in your account, such as editing the fields of a product, modifying a repricing rule, or reading the prices of all competitors for a product.
Our API and User Interface (UI) execute the same functions in our back-end, so almost every action you can perform in the UI can be executed via API.
When you send a request via API your JSON data contains an array of all the actions you wish to perform, from 1 to 1000 maximum per request. So you can rename 3 products and create a new category, all in a single request of 4 actions. You can also just read the data of 1000 products at once with 1000 "productGet" actions.
Actions are executed in order of the array, so if you create a new repricing rule and then assign products to it you must put these in this order in your request so the rule exists by the time the products are edited.
Please scroll down for the list of all available actions.

Your JSON request object

For scripts and AI-generated code: The reference examples below include // comments and ... placeholders to explain fields. They are illustrations, not valid JSON to send unchanged. Remove comments and placeholders before sending a request. The two curl examples above contain valid JSON bodies.

Your entire POST data should be a single JSON object {...} containing the array of actions to perform.
{
	"stopOnFailure"	: 1,  // (optional) By default our back-end normally continues to the next action upon failure. Setting this to 1 prevents it from continuing.

	// Array of all actions :
	"actions" : [
		// Action 1 :
		{
			"a"	: "someActionName",  // The action name is always the "a" variable of each action object
			"xx"	: "some parameter this action needs"	// everything besides "a" is whatever the action needs
		},
		// Action 2 :
		{
			"a"	: "anotherActionName",
			"abc"	: "some parameter this action needs",
			"xyz"	: {"x":"some object this action needs", "y":5, "z":6}
		},
		// Action 3 :
		{
			"a"	: "aThirdAction",
			"xyz"	: ["some array this action needs", "..."]
		}
	]
}

Server response and HTTP codes

The response data our server returns is text/plain for any error related to the format of your request, or if your account is disabled for example. In such cases the text data is the error message.
If your request was correct you should always get a JSON object as a response, except for a few actions that are marked as exceptions (e.g. downloading a spreadsheet). You will know this by our Content-Type: application/json response header.
Our HTTP response code is:
► 200 when all the actions succeeded (the neutral outcome ok=0 is considered a success)
► 406 when some actions succeeded while others failed.
► 422 when all actions failed, but not for reasons listed below.
► 400 when the JSON object we received could not be decoded.
► 403 when the authentication failed (typically wrong API key).
If you feel lazy you can easily trust these response codes in your scripts. Otherwise the JSON object returned will give you more information:
{
	"ok"		: 15,  // number of actions successfully performed (action ok>0)
	"errors"	: 0,  // number of actions that generated an error (action ok<0)
	"actionsCount"	: 20,  // total number of actions initially requested (the size of your actions array)
	// NOTE: we can conclude from this that 5 actions returned ok=0 (meaning that they didn't fail but they resulted in no change)
	
	// Array of all actions initially requested (same order) :
	"actions" : [
		...
		// Response object of each action:
		{
			"a"	: "someActionName",
			"ok"	: 4,		// Outcome. >0 = success, 0 = nothing changed, <0 = error
						// 0 can happen for example if you change the price of a product that is already at this price.
						// -1 would happen if that product doesn't exist.
						// >0 depends on the action. It is often the number of fields/things actually modified.
			"error"	: "...",  // Error message, if ok < 0
						// Our error strings are always very detailed. You should log them
						// because they will provide everything you need to understand what went wrong.
			...	// Whatever else the action returns that is specific to it
		},
		...
	]
}

Examples?

Sometimes everything is clearer with examples. Check out more examples at the very bottom of this page.

Rate limits and scheduling

We do have a firewall protecting us (and therefore you) from attacks or floods of requests. When our firewall blocks a host it will give the impression that our website is down (no connection, no response, no ping).
To make sure this does not happen to your legitimate requests please:
► Never use the wrong API key because the firewall has a poor tolerance for those (same for wrong passwords on our website)
► Group many actions as a single request in the same JSON object (1000 max). So if you create 1000 products do not send 1000 requests but only one.
► If you absolutely need to make a lot of requests put a 500ms sleep between them and reuse the same HTTPS connection (it is a common mistake to open a new connection for every request). Making a lot of requests is only justified if you need to update hundreds of thousands of products at once (you will put 1000 per request).
We do not have a fixed request-rate limit, our firewall algorithm makes decisions based on dozens of parameters. Common sense would be your best guide in this case.
 
Scheduled requests:
If you schedule your requests at regular intervals please put a random sleep time to make them at an awkward second because most of our customers make their requests at the exact same time (second 0 of a minute). This will make your requests much faster and less likely to timeout in case of server overload.

All available API actions

Each action below is documented with its parameters and examples of requests and responses. The navigation on the left links directly to every action.

Products

ActionPurpose
productCreateCreate a new product
productEditEdit an existing product
productCompetitorsEdit competitor URLs of a product (without having to overwrite them all)
productCategoriesEdit categories of one or many products (without having to overwrite them all)
productRepricingAssign a lot of products to a repricing rule
productDeleteDelete one or many products
productGetRetrieve a product (and its competitors data)
productGetAllRetrieve all products (and their competitors data)

Categories

ActionPurpose
categoryCreateCreate a new category
categoryEditEdit an existing category
categoryDeleteDelete one or many categories
categoryCopyCopy/Move products from categori(es) to other(s)
categoryGetRetrieve a category
categoryGetAllRetrieve all categories

Alerts

ActionPurpose
alertRecipientCreateCreate a new alert recipient
alertRecipientEditEdit an alert recipient and/or its alert rules
alertRecipientDeleteDelete an alert recipient
alertRuleEditEdit an existing alert rule for a recipient
alertRuleCreate ?There is no such action, use alertRecipientEdit instead and provide a "rulesAdd" array
alertRuleDelete ?There is no such action, use alertRecipientEdit instead and provide a "rulesRemove" array
alertGetAllRetrieve all alert recipients and their alert rules

Repricing rules

ActionPurpose
repricingCreateCreate a new repricing rule
repricingEditEdit an existing repricing rule
repricingDeleteDelete a repricing rule
repricingGetAllRetrieve all repricing rules

Spreadsheets

ActionPurpose
spreadsheetDownloadDownload a spreadsheet
spreadsheetPresetEditRename/Delete a spreadsheet preset
spreadsheetAutoCreateCreate a new spreadsheet auto-export
spreadsheetAutoEditEdit a spreadsheet auto-export

Competitors & More

ActionPurpose
competitorDeleteAllDelete a competitor from all products or categori(es)
mathEditSet/Edit the math of a competitor domain

Products

productCreate - Create new product

This is the same function the UI uses in the New Product page.
{ "actions" : [
	...
	{
		"a"		: "productCreate",
		"code"		: "UNIQUE-REF-CODE",  // 1 to 80 max ([a-z\d][a-z\d_-]{0,78}[a-z\d]) Case-insensitive (converted to uppercase)
		"name"		: "The Product Name",  // 1 to 200 max
		"price"		: 1234.90,  // Watch out for commas and dots. No comma for thousands. Decimal point is a dot.
		"currency"	: "USD",
		"cost"		: 1234.90,  // (optional)
		"costCurrency"	: "USD",  // mandatory if cost
		"repricingRule"	: "The Rule Name",  // (optional) Repricing rule assigned (empty = disabled, non-existing = failure)
		"myWebsite"	: "https://...",  // (optional) URL or shopify:productId:variantId, or woocommerce:productId(:variationId)
		"competitors"	: ["https://...", "https://..."],  // (optional)
		"categories"	: ["The Category 1", "The Category 2"],  // (optional) categori(es) the product belongs to (non-existing = creates it)
		"comments"	: "Some comments\nAnd another line",  // (optional) 1000 UTF-8 characters max
		"custom_someFieldName1"	: "Value of custom field",		// (optional)
		"custom_someFieldName2"	: "Value of other\nCustom\tField",  // (optional)
		// Custom fields are for you to use internally for whatever purpose. They can be used in many places in your account, e.g. as columns when you export a spreadsheet.
		// You can have up to 20 custom fields per product. Their name is limited to 40 letters/numbers/underscore only (case-sensitive, not including the "custom_" prefix so 47 total).
		// Their value is limited to 500 bytes (characters typically, of any kind). You can set custom field values as a number (custom_varname:123)
		// but we will always return them as a string (custom_varname:"123") so remember to convert what you get from the API if necessary.
		// To delete a custom field simply set its value to "" and it will be removed entirely. Obviously this is not something that would happen in productCreate but in productEdit.
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "productCreate",
			"ok"	: 1,
			"code"	: "REF-CODE",  // Final code in database (because we may convert your code if it contains special characters e.g.)
		},
		...
	]
}

productEdit - Edit existing product

This is the same function the UI uses when you edit a field in the page of a product, except that the UI sends one field at a time as you edit it, while the API allows you to edit the name and price in a single call for example.
{ "actions" : [
	...
	{
		"a"		: "productEdit",
		"code"		: "REF-CODE",
		...		// Takes the same parameters as productCreate, all of them optional.
		"newCode"	: "REF-CODE-RENAMED",  // (optional)
		"competitors"	: ["https://...", "https://..."],  // If present, OVERWRITES all existing competitor URLs.
		// If you only want to add or remove competitors to/from existing ones you need to use the productCompetitors action instead.
		"categories"	: ["The Category 1", "The Category 2"],  // if present, OVERWRITES current categories. Use productCategories or categoryEdit instead.
		"custom_someFieldName"	: "New value or empty to delete that custom field",
		// Keep in mind custom fields are limited to 20 per product, so if you want to add more you may need to first delete old ones by setting their value to "".
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "productEdit",
			"ok"	: 3,  // Number of changes made (e.g. my price + 2 competitor URLs = 3)
			"code"	: "REF-CODE",  // Final (new) code in database (because we may convert your code if it contains special characters e.g.)
		},
		...
	]
}

productCompetitors - Edit competitor URLs of a product

Edit the competitor URLs of a product without having to overwrite them all (unlike the productEdit action).
{ "actions" : [
	...
	{
		"a"			: "productCompetitors",
		"code"			: "REF-CODE",
		"competitorsAdd"	: ["https://...", "https://..."],  // (optional) Add these competitor URLs to existing ones
		"competitorsRemove"	: ["https://...", "example.com/a0ff80...5f00", "example.net"],  // (optional) Remove these competitors. Each one can be:
		// > The URL (including any hashtag modifier you may have added). Labels are optional if you have any.
		// > The competitor "ref" (as returned by other actions, which is the root domain followed by an md5 of 32 characters)
		// > The root domain or full hostname, in which case all competitor URLs from this domain will be removed from the product.
		// NOTE: "competitorsAdd" and "competitorsRemove" can also be strings instead of arrays of a single element.
		// Remove is performed before Add so you can remove an entire domain and then add a URL from that domain.
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"			: "productCompetitors",
			"ok"			: 3,  // Number of competitor URLs modified
			"competitorsAdded"	: 2,  // Number of competitor URLs added to the product
			"competitorsRemoved"	: 1,  // Number of competitor URLs removed from the product
			// ERRORS:
			// > Adding a URL that is already present will NOT generate an error (ok = 0)
			// > Removing a full URL or URL "ref" that is NOT present will generate an error (ok = -1)
			// > Removing all URLs of a domain that is NOT present will NOT generate an error (ok = 0)
		},
		...
	]
}

productCategories - Edit categories of one or many products

Edit the categories of product(s) without having to overwrite them all (unlike the productEdit action).
This is the same function the UI uses when you go to All Products, select some product(s), then click Selected Products > Edit Categories, so you might want to go have a look there for a more detailed explanation.
You may want to look at the categoryEdit action as well so you can use whichever you prefer.
{ "actions" : [
	...
	{
		"a"			: "productCategories",
		"code"			: "REF-CODE",  // case-insensitive (string), OR:
		"code"			: ["REF-CODE-1", "REF-CODE-2"],  // can also be an array with multiple products (100,000 limit)
		"categoriesRemove"	: ["The Category 1", "The Category 2"],  // (optional) First remove from these categories.
		"categoriesRemoveAll"	: 1,  // (optional) First remove from all categories (just like productEdit)
		"deleteEmptyCategories"	: 1,  // With this any category that becomes empty
		// as a result of having removed product(s) from it will be deleted.
		"categoriesAdd"		: ["The Category 1", "The Category 2"],  // (optional) Then add to these categories (non-existing = creates them)
		"categoriesAddClear"	: 1,  // (optional) First empty these categories we add the product(s) to
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "productCategories",
			"ok"	: 3,  // Number of products modified
		},
		...
	]
}

productRepricing - Assign a lot of products to a repricing rule

Assign up to 100,000 products to one Repricing Rule.
This does the same thing as the productEdit action, except that since each request is limited to 1,000 actions you would need to make 50 requests if you wanted to assign 50,000 products to a specific Repricing Rule, and the execution would be much slower.
If you only want to modify a few products you may want to use the productEdit action instead.
{ "actions" : [
	...
	{
		"a"			: "productRepricing",
		"repricingRule"		: "The Rule Name",  // case-insensitive, "" (empty string but existing) means no rule (disable repricing)
		"products"		: ["REF-CODE-1", "REF-CODE-2"],  // case-insensitive (100,000 limit)
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"		: "productRepricing",
			"ok"		: 10,  // Number of products modified
			// So if all products were already assigned to that rule you will get ok=0.
		},
		...
	]
}

productDelete - Delete one or many products

WARNING: the product and its competitor URLs are immediately deleted from your account without any way of restoring them.
This is the same function the UI uses when you go to All Products, select some product(s), then click Selected Products > Delete.
{ "actions" : [
	...
	{
		"a"	: "productDelete",
		"code"	: "REF-CODE",  // case-insensitive (string), OR:
		"code"	: ["REF-CODE-1", "REF-CODE-2"],  // can also be an array to delete multiple products (100,000 limit)
		"deleteEmptyCategories"	: 1,  // With this any category that becomes empty
		// as a result of having deleted this/these product(s) will be deleted as well.
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "productDelete",
			"ok"	: 2,  // Number of products successfully deleted
		},
		...
	]
}

productGet - Retrieve a product

{ "actions" : [
	...
	{
		"a"			: "productGet",
		"code"			: "REF-CODE",  // case-insensitive
		"currency"		: "EUR",  // (optional) convert all amounts to this currency instead of the product price currency
		"withStats"		: 1,  // (optional) return the stats object
		"noCompetitors"		: 1,  // (optional) do not return the competitors array (to keep the data smaller)
		"noComments"		: 1,  // (optional) do not return the comments (...)
		"withScreenshots"	: 1,  // (optional) return the URL to latest screenshot image for each competitor
		"sickMax"		: 2,  // (optional, default 1) 0-3 return only competitor URLs with a sickness level up to N
	},
	...
] }
This returns all information about a product, including the current price and stock of competitors.
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"			: "productGet",
			"ok"			: 1,
			"code"			: "REF-CODE",
			"name"			: "The Product Name",
			"price"			: 1234.90,  // Your own price, always in original currency
			"currency"		: "USD",  // Original currency of "price"
			"convCurrency"		: "EUR",  // Currency in which converted amounts are shown (either "currency" provided, or product price currency)
			"priceConv"		: 1234.90,  // Your own price converted using "convCurrency", or same as "price"
			"cost"			: 1234.90,
			"costCurrency"		: "USD",
			"costPriceConv"		: 1234.90,  // Your cost converted into price "currency"
			"costConv"		: 1234.90,  // Your cost converted into "convCurrency"
			"margin"		: 10.5,  // Percentage (can be negative)
			"repricingRule"		: "The Rule Name",  // These 3 repricing variables are ABSENT when there is no repricing
			"reprice"		: 1234.90,  // Current reprice amount calculated based on the repricingRule
			"repriceCurrency"	: "USD",  // Same as "convCurrency", just here for clarity
			"myWebsite"		: "https://...",
			"created"		: 1234567890,  // Epoch time at which the product was created
			"createdBy"		: "SOMEUSER",  // Username or "API"
			"edited"		: 1234567890,  // Epoch time of last edit (if ever)
			"editedBy"		: "SOMEUSER",  // Username or "API"
			"comments"		: "Some comments\nAnd another line",
			"commentsLast"		: 1234567890,  // Epoch time of last comments edited (if ever)
			"commentsLastBy"	: "SOMEUSER",  // Username or "API"
			"categories"		: ["The Category 1", "The Category 2"],
			"stats"		: {
				// Only competitor URLs of sickness level 0 and 1 count in the stats, regardless of your "sickMax".
				// All amounts are in "convCurrency", which means either the "currency" you provided or the product original "currency".
				// WARNING: when there is no amount (e.g. compIn* but no one is in stock) the value is 0.
				"rank"			: 1.23,  // Your rank in price (1 = no one is below you)
				"rankIn"		: 1.23,  // Your rank among in-stock competitors
				"compCount"		: 3,  // Nb of competitors having a valid price
				"compInCount"		: 3,  // Nb of in-stock competitors ...
				"compOutCount"		: 3,  // Nb of out-of-stock competitors ...
				"compAvg"		: 123.56,  // Average of all competitors
				"compAvgVsMe"		: 123.56,  // The gap (in convCurrency) between your price and competitors average (negative = you are lower)
				"compInAvg"		: 123.56,  // Average of in-stock competitors
				"compInAvgVsMe"		: 123.56,  // Same as "compAvgVsMe" but only in-stock competitors
				"compLowest"		: 123.56,  // Lowest competitor
				"compLowestVsMe"	: 123.56,  // Same as "compAvgVsMe" but for lowest instead of average
				"compInLowest"		: 123.56,  // Lowest competitor in-stock
				"compInLowestVsMe"	: 123.56,  // Same as "compInAvgVsMe" but ...
				"compHighest"		: 123.56,  // Highest competitor
				"compHighestVsMe"	: 123.56,  // Same as "compAvgVsMe" but ...
				"compInHighest"		: 123.56,  // Highest competitor in-stock
				"compInHighestVsMe"	: 123.56,  // Same as "compInAvgVsMe" but ...
			},
			"competitors"	: [
				// Array of competitors (cheapest first) where each competitor is an object :
				...
				{
					"url"		: "https://...",  // includes any hashtag modifier you may have put
					"ref"		: "example.com/123abc...def",
					// URL reference (which is the root domain followed by an md5 of 32 characters). This is how we identify URLs internally in our system.
					"domain"	: "example.com",  // root domain of this competitor
					"price"		: 1234.90,  // last amount found on the website (after any math based on your settings)
					"currency"	: "USD",  // currency found on the website
					"priceConv"	: 1234.90,  // price converted to "convCurrency"
					"stock"		: "5",  // inventory (string). "A" = available, "2" = 2 in stock, "0" = out of stock, "" = we don't know.
					"salesWk"	: 123,  // estimated sales/week (if we have - see question-mark in product page for explanation)
					"screenshotURL"	: "https://pricezag.com/.../screenshot.png",
					// if "withScreenshots", URL to the latest screenshot we have (no need to login, access is public with this long URL)
					"confidence"	: 98,
					// Percentage of confidence that we picked the right price (see explanation by clicking (?) in a product page)
					// Below 70% (configurable) is what we call "unreliable" in our platform by displaying a question mark next to the price.
					// This is independent from sickness. A URL can be sick but we know we picked the right price at the time,
					// or it can be healthy (we could visit) but we're not sure we picked the correct price in the page.
					"sick"		: 0,  // URL sickness level from 0 to 3
					// 0 : Our latest crawling worked (meaning we could visit and found a price in the page)
					// 1 : Our latest crawling failed but we succeeded recently (nothing to worry about, it happens all the time)
					// 2 : We could not get a valid price for many days
					// 3 : We could not get a valid price for many weeks
					// NOTE: the "price" and "priceConv" fields are only present for URLs of sickness level up to 1. Sickness 2 and 3 will have no price.
				},
				...
			],
			"custom"	: {
				// These are your custom fields. If you don't know how they work please open
				// a product page in the UI and click the (?) icon next to the CUSTOM FIELDS box.
				// When there is none, this entire "custom" object is absent.
				"variable_1"	: "value 1",  // Values are always strings, even when they are in fact numbers
				"variable_2"	: "value 2",  // and there is no such thing as an empty value, this would have deleted the variable.
				...
			}
		},
		...
	]
}

productGetAll - Retrieve all products

Please make sure your timeout settings are high because this may take our server some time depending on the number of products and competitors involved.
{ "actions" : [
	...
	{
		"a"			: "productGetAll",
		"currency"		: "EUR",  // (optional) convert all amounts to this currency instead of each product price currency
		"withStats"		: 1,  // (optional) return the stats object
		"noCompetitors"		: 1,  // (optional) do not return the competitors array (to keep the data smaller)
		"noComments"		: 1,  // (optional) do not return the comments (...)
		"withScreenshots"	: 1,  // (optional) return the URL to latest screenshot image for each competitor
		"sickMax"		: 2,  // (optional, default 1) 0-3 return only competitor URLs with a sickness level up to N
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"		: "productGetAll",
			"ok"		: 10,  // Total number of products
			"products"	: [ // array of products (alpha order of ref-code) where each product is an object :
				...
				{ ... },  // Product object just like the response of the productGet action (just above)
				...
			],
		},
		...
	]
}
You may also like the categoryGet action.

Categories

categoryCreate - Create a new category

This is the same function the UI uses in the Categories page when you click the New Category button, except that with the API you can also assign products while creating them.
Unlike other actions that are flexible with the existence of categories (e.g. productCreate accepts both existing and new categories) this action will fail if the categori(es) already exist.
{ "actions" : [
	...
	{
		"a"		: "categoryCreate",
		"name"		: "The New Category Name",
		"name"		: ["New Category 1", "New Category 2"],  // can also be an array
		"products"	: ["REF-CODE-1", "REF-CODE-2"],  // (optional) Put these products in this/these categori(es) as we create it/them
		"comments"	: "Some comments\nAnd another line",  // (optional) 1000 UTF-8 characters max
	},
	...
] }

categoryEdit - Edit an existing category

You may want to look at the productCategories action as well so you can use whichever you prefer.
{ "actions" : [
	...
	{
		"a"			: "categoryEdit",
		"name"			: "The Category Name",		// case-insensitive
		"newName"		: "The Category Renamed",  // (optional)
		"products"		: ["REF-CODE-1", "REF-CODE-2"],  // (optional) Overwrite list of products
		"productsAdd"		: ["REF-CODE-1", "REF-CODE-2"],  // (optional) Add these products to the category
		"productsRemove"	: ["REF-CODE-3", "REF-CODE-4"],  // (optional) Remove these products from the category (does NOT delete them)
		"comments"		: "To overwrite the comments.",  // (optional) 1000 UTF-8 characters max
	},
	...
] }
So "products", "productsAdd" and "productsRemove" all do the same thing, they change which products are part of this category, but they do it different ways. To remove all products from the category simply use "products" : []
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"			: "categoryEdit",
			"ok"			: 5,  // Number of modifications made
			// (renaming and editing comments each count as one, and so does each product added/removed)
			"name"			: "The Category Name",  // new name if renamed, or same old name
			"productsCount"		: 30,  // New number of products in this category (whether changed or not)
			"productsAdded"		: 2,  // Number of products added to the category
			"productsRemoved"	: 2,  // Number of products removed from the category
		},
		...
	]
}

categoryDelete - Delete one or many categories

This is the same function the UI uses when you go to Categories, select some categori(es), then click Selected Categories > Delete.
{ "actions" : [
	...
	{
		"a"			: "categoryDelete",
		"name"			: "The Category Name",  // case-insensitive (string), OR:
		"name"			: ["Category 1", "Category 2"],  // can also be an array to delete multiple categories (1000 max)
		"deleteProducts"	: 1,
		// By default the category is deleted while its products drop out of it,
		// but with deleteProducts all products in the category are deleted without confirmation,
		// and the competitor URLs are deleted along with them without any way of restoring them!
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"			: "categoryDelete",
			"ok"			: 2,  // Number of categories deleted
			"productsCount"		: 123,  // Number of products concerned by this/these categori(es)
			"productsDeleted"	: 123,  // Number of products deleted as a result of the "deleteProducts" option
			// NOTE: a product that is in many of the categories provided will only count once
		},
		...
	]
}

categoryCopy - Copy/Move products from categori(es) to other(s)

Copy or move products from matching categori(es) to other new and/or existing categori(es). This can be used to MERGE categories (see examples at the end of this page).
There is nothing you can do here that will delete any product, they are simply moved in and out of categories.
This is the same function the UI uses when you go to Categories, select some categori(es), then click Selected Categories > Copy & More, so you might want to go have a look there for a more detailed explanation.
{ "actions" : [
	...
	{
		"a"			: "categoryCopy",
		"categoriesFrom"	: ["Category 1", "Category 2"],  // Match the products from these categori(es) (the source)
		"removeMode"		: "cat",  // (optional) Remove corresponding products from the "categoriesFrom" provided
		"removeMode"		: "all",  // (optional) Remove corresponding products from all categories they are in
		// So if removeMode is omitted the products remain in "categoriesFrom" as they are also added to "categoriesTo" (copy instead of move)
		"deleteMode"		: "cat",  // (optional) Delete the "categoriesFrom" provided after copying products
		"deleteMode"		: "all",  // (optional) Delete all categories these products are in
		// So if deleteMode is omitted the "categoriesFrom" will still exist even after the products were removed from them with "removeMode".
		"categoriesTo"		: ["Another Category 1", "Another Category 2"],
		// Put all matching products into this/these destination categori(es) (non-existing = creates them)
		"categoriesToClear"	: 1,  // (optional) First empty these categories we add the product(s) to
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"			: "categoryCopy",
			"ok"			: 3,  // Number of categories altered (both source and destination)
			// "ok" is the sum of categoriesModified + categoriesCreated + categoriesDeleted
			"categoriesModified"	: 2,  // Number of existing categories modified (both source and destination)
			"categoriesCreated"	: 1,  // Number of new categories created
			"categoriesDeleted"	: 0,  // Number of categories deleted as a result of "deleteMode"
			"productsCount"		: 123,  // Number of products matching the "categoriesFrom" provided
			// NOTE: a product that is in many of the "categoriesFrom" provided will only count once
		},
		...
	]
}

categoryGet - Retrieve a category

{ "actions" : [
	...
	{
		"a"		: "categoryGet",
		"name"		: "The Category Name",  // case-insensitive
		"currency"	: "EUR",  // (MANDATORY if withStats or withProducts) currency to use for stats
		"withStats"	: 1,  // also return the statistics (slower)
		"withProducts"	: 1,  // also return the products of this category (can result in large data)
		"noCompetitors"	: 1,  // if withProducts, do not return the competitors array (to keep the data smaller)
		"noComments"	: 1,  // if withProducts, do not return the comments of products (still return category comments)
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"			: "categoryGet",
			"ok"			: 1,
			"name"			: "The Category Name",
			"created"		: 1234567890,  // Epoch time
			"createdBy"		: "SOMEUSER",  // Username or "API"
			"comments"		: "Comments of this category",
			"commentsLast"		: 1234567890,  // Epoch time of last comments edited (if ever)
			"commentsLastBy"	: "SOMEUSER",  // Username or "API"
			"productsCount"		: 10,  // Number of products in this category
			"stats"		: {
				// These stats are the ones shown in the UI in a category page and as columns in the list of categories.
				// For more details about each of them select them there as a column in the list and click (?).
				// All amounts are in the "currency" you provided in the request.
				// WARNING: when there is no amount (e.g. compIn* but no one is in stock) the value is 0.
				"priceAvg"		: 123.45,  // Your own average price
				"costAvg"		: 123.45,  // Only products that have a cost count in this average
				"marginAvg"		: 10.5,  // Percentage (can be negative, only products with a cost count)
				"rankAvg"		: 1.23,  // Your average rank in price (1 = no one is below you)
				"rankInAvg"		: 1.23,  // Your average rank among in-stock competitors
				"productsCountComp"	: 9,  // Number of products having competitor(s)
				"productsCountCompIn"	: 7,  // Number of products having in-stock competitor(s)
				"compAvg"		: 123.56,  // Average of all competitors averages
				"compAvgVsMe"		: 123.56,  // The average gap (in currency) between your price and competitors (negative = you are lower)
				"compInAvg"		: 123.56,  // Same as "compAvg" but only in-stock competitors
				"compInAvgVsMe"		: 123.56,  // Same as "compAvgVsMe" but only in-stock competitors
				"compLowest"		: 123.56,  // Same as "compAvg" but average of lowest competitor in each product
				"compLowestVsMe"	: 123.56,  // Same as "compAvgVsMe" but ...
				"compInLowest"		: 123.56,  // Same as "compInAvg" but ...
				"compInLowestVsMe"	: 123.56,  // Same as "compInAvgVsMe" but ...
				"compHighest"		: 123.56,  // Same as "compAvg" but average of highest competitor in each product
				"compHighestVsMe"	: 123.56,  // Same as "compAvgVsMe" but ...
				"compInHighest"		: 123.56,  // Same as "compInAvg" but ...
				"compInHighestVsMe"	: 123.56,  // Same as "compInAvgVsMe" but ...
			},
			"products"	: [ // array of products (alpha order of ref-code) where each product is an object :
				...
				{ ... },  // Product object just like the response of the productGet action
				...
			],
		},
		...
	]
}

categoryGetAll - Retrieve all categories

Unlike categoryGet, the categoryGetAll action does not return an object of each product's detail. We do this because the same product can belong to multiple categories so this could result in very large and redundant data. If you want the products content you probably want to either use categoryGet on each category, or productGetAll and then sort them based on their categories.
{ "actions" : [
	...
	{
		"a"		: "categoryGetAll",
		"withStats"	: 1,  // also return the statistics (slower)
		"noComments"	: 1,  // do not return the comments (to keep the data smaller)
		"noProducts"	: 1,  // do not return the array of products (...)
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"		: "categoryGetAll",
			"ok"		: 10,  // Total number of categories (length of the "categories" array)
			"categories"	: [ // array of categories (alpha order) where each category is an object :
				...
				{
					"name"			: "The Category Name",
					"created"		: 1234567890,  // Epoch time
					"createdBy"		: "SOMEUSER",  // Username or "API"
					"comments"		: "Comments of this category",
					"commentsLast"		: 1234567890,  // Epoch time of last comments edited (if ever)
					"commentsLastBy"	: "SOMEUSER",  // Username or "API"
					"productsCount"		: 2,  // Number of products in this category
					"products"	: ["REF-CODE-1", "REF-CODE-2"],  // ref-codes of its products (alpha order) unless noProducts=1
					"stats"		: {
						// If "withStats", same stats object as the categoryGet action (just above)
						...
					},
				}
				...
			],
		},
		...
	]
}

Alerts

alertRecipientCreate - Create a new alert recipient

This is the same function the UI uses when you go to Alerts and click the New Recipient button.
Please make sure to click the green question-mark icons in your account for details about each field.
{ "actions" : [
	...
	{
		"a"		: "alertRecipientCreate",
		"email"		: "john@example.net",  // can also be a phone number for SMS alerts - see help in the UI
		"name"		: "john",  // A simple display name (typically their first name, case-insensitive)
		"maxFrequency"	: 1800,  // (optional, default 7200) see help in the UI
		"simplify"	: 0,  // (optional, defaults to 1) Enable simplification (see help in the UI)
		"sleepEnabled"	: 1,  // (optional) Enable sleep time period
		"sleepFrom"	: "18:30",  // (only if sleepEnabled) 24-hour format
		"sleepTo"	: "07:00",  // (only if sleepEnabled) 24-hour format
		"timezone"	: "America/Los_Angeles",  // (only if sleepEnabled) Format is file path of Linux folders /usr/share/zoneinfo
				// Spaces will be converted to underscores.
				// Please go find your timezone in our [New Recipient] form in the UI to make sure you get it exactly right.
		"status"	: "hold",  // (optional) set "hold" or "disable" mode. Any other value (like "") is active.
		"rules"		: [...],  // (optional) Initial alert rules (array of rule objects) just like "rulesAdd" in alertRecipientEdit below
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "alertRecipientCreate",
			"ok"	: 1,
		},
		...
	]
}

alertRecipientEdit - Edit an alert recipient and/or its alert rules

This is the same function the UI uses when you go to Alerts and click the Settings or Rules button for a recipient.
Please make sure to click the green question-mark icons in your account for details about each field.
The "email" variable is what determines which recipient will be updated. All the other fields modify that recipient's settings.
{ "actions" : [
	...
	{
		"a"		: "alertRecipientEdit",
		"email"		: "john@example.net",  // current email or phone number (exact same format as returned by the alertGetAll action)
		"newEmail"	: "john2@example.net",  // (optional) new email or phone number - see help in the UI
		"name"		: "john2",  // (optional) new name
		"maxFrequency"	: 1800,  // (optional) see help in the UI
		"simplify"	: 1,  // (optional) Enable simplification (see help in the UI)
		"sleepEnabled"	: 1,  // (optional) Enable sleep time period
		"sleepFrom"	: "18:30",  // (only if sleepEnabled) 24-hour format
		"sleepTo"	: "07:00",  // (only if sleepEnabled) 24-hour format
		"timezone"	: "America/Los_Angeles",  // (only if sleepEnabled) see alertRecipientCreate action above
		"status"	: "hold",  // (optional) set "hold" or "disable" mode. Any other value (like "") turns into "active".
		"rules"		: [{ruleObject}, {ruleObject}],  // (optional) Overwrite all existing rules (empty array to remove all)
				  // Do not provide any "rules" array if you want to keep existing rules
		"rulesAdd"	: [ // (optional) Will add these rules to existing ones
			...
			{  // THIS IS A RULE OBJECT FORMAT :
				"name"		: "me_cross_avg",  // see Alerts page > [Rules] > [+ New Rule] then select a rule to show its API name
				"disabled"	: 1,  // (optional) It would be silly to create a disabled rule, but the alertRuleEdit action below may use it
				"var_n"		: 123.00,  // An "X" variable becomes "var_x",
				"var_c"		: "USD",  // ... so these should be whatever the rule needs,
				"var_h"		: 24,  // ... (some of them don't have any variable).
				"direction"	: "both",  // Or "drop" or "rise", if the rule has a direction
				"categories"	: ["The Category 1", "The Category 2"],  // (optional) Empty or omitted means ALL PRODUCTS
				"ignoreOos"	: 1,  // (optional) Ignore out-of-stock competitors (see UI)
				"urlInclude"	: "",  // (optional, case-insensitive)
				"urlExclude"	: "amazon",  // (optional, case-insensitive)
				// "ruleNumber" is what identifies each rule (e.g. to edit/delete them later).
				// You will see it in the rule objects when you read them but you cannot set this.
			}
			...
		],
		"rulesRemove"	: [1,4,7],  // (optional) the "ruleNumber" of the rules to remove
					// You will get this "ruleNumber" from the alertGetAll action below.
					// NOTE: to remove all rules simply use "rules" = [] instead.
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "alertRecipientEdit",
			"ok"	: 3,  // number of changes made (variables + number of rules added/deleted)
		},
		...
	]
}

alertRecipientDelete - Delete an alert recipient

This is permanent. All alerts rules of this recipient and their history will be instantly deleted with no way of restoring them.
{ "actions" : [
	...
	{
		"a"		: "alertRecipientDelete",
		"email"		: "john@example.net",  // current email or phone number (exact same format as returned by the alertGetAll action)
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "alertRecipientDelete",
			"ok"	: 1,
		},
		...
	]
}

alertRuleEdit - Edit an existing alert rule for a recipient

This allows you to edit an individual existing alert rule as opposed to replacing or adding some. You would typically use this to change the N variable for example, or to enable/disable a rule.
To remove a rule please use the "rulesRemove" array from the alertRecipientEdit action instead, and to add a new one use the "rulesAdd" array from there too.
{ "actions" : [
	...
	{
		"a"		: "alertRuleEdit",
		"email"		: "john@example.net",  // email or phone number of the recipient (exact same format as returned by the alertGetAll action)
		"ruleNumber"	: 1,  // Ref number of the rule to edit, as returned by the alertGetAll action below.
		"rule"		: {
			// Variable(s) of the rule to overwrite - See "rulesAdd" in alertRecipientEdit above for RULE OBJECT FORMAT.
			"disabled"	: 1,  // for example, to enable/disable a rule
			"var_n"		: 5,  // or any other variable, like N for example, depending on the kind of rule
			...
		},
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "alertRuleEdit",
			"ok"	: 1,  // number of variables modified
		},
		...
	]
}

alertGetAll - Retrieve all alert recipients and their alert rules

{ "actions" : [
	...
	{
		"a"	: "alertGetAll",
		// This action does not take any parameters
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"		: "alertGetAll",
			"ok"		: 3,  // number of recipients (which is really just the size of the "recipients" array)
			"recipients"	: [ // array of all recipients, each being an object just like in the alertRecipientCreate action above
				...
				{
					"email"		: "john@example.net",
					"name"		: "john",
					"maxFrequency"	: 1800,
					"simplify"	: 0,
					"sleepEnabled"	: 1,
					"sleepFrom"	: "18:30",
					"sleepTo"	: "07:00",
					"timezone"	: "America/Los_Angeles",
					"status"	: "active",
					"rules"		: [ // array of rules, each being an object as described in the alertRecipientEdit action above
						...
						{
							"ruleNumber"	: 4,  // Reference number to use if you need to edit this rule using the alertRuleEdit action above
							"name"		: "me_cross_avg",
							"disabled"	: 0,
							"var_n"		: "123.00",  // NOTE: we return all vars as strings even when they are numbers
							"var_c"		: "USD",
							"var_h"		: "24",
							"direction"	: "both",
							"categories"	: [],  // empty means all products
							"ignoreOos"	: 0,
							"urlInclude"	: "",
							"urlExclude"	: "amazon",
						}
						...
					],
				}
				...
			],
		},
		...
	]
}

Repricing rules

repricingCreate - Create a new repricing rule

This is the same function the UI uses when you go to Repricing and click the New Repricing Rule button.
Please make sure to click the green question-mark icons in your account for details about each field.
But how do I assign products to the rule? By editing the products themselves, using the productEdit or the productRepricing action. A rule doesn't really "have" products, it's the products that each use a specific rule by pointing to it.
{ "actions" : [
	...
	{
		"a"	: "repricingCreate",
		"name"	: "The Rule Name",  // 50 characters maximum
		"init"	: { // object of initializer fallback (the first row called "Set my price to")
			"target"	: "cheapest_n",  // (see list below)
			"var_n"		: 3,  // only if the target requires an N variable
			"plusAmount"	: -2,  // positive for above, negative for below
			"plusCurrency"	: "%",  // currency (e.g. "USD") or "%"
			// So this corresponds to "Set my price 2% below average of 3 cheapest competitors"
			// WARNING: unlike the "never" objects below there is no "direction" for the initializer. It's the sign of "plusAmount" that determines below/above.
		},
		"never"	: [ // (optional) "but never" objects, executed in order (0 to 10 of them maximum)
			{
				"target"	: "cheapest_n_in",  // (see list below)
				"var_n"		: 1,  // only if the target requires an N variable
				"direction"	: "below",  // or "above"
				"plusAmount"	: -10,  // positive for plus, negative for minus (mandatory even if 0)
				"plusCurrency"	: "USD",  // currency or "%" (mandatory even if plusAmount=0)
				// So this corresponds to "But never below the cheapest competitor in-stock minus 10.00 USD"
			},
			{
				"target"	: "cost",
				"direction"	: "below",
				"plusAmount"	: 5,
				"plusCurrency"	: "%",
				// So this corresponds to "But never below my cost plus 5%"
			}
		],
		"ignoreDomains"	: "amazon ebay.com MyLabel1",  // (optional) domains and/or labels to ignore from competitors (case-insensitive, see UI)
		"charm"		: "none|zeros|charm",  // (optional, defauts to "none") charm pricing ("", "none" or omitted all result in the same thing)
		"confidence"	: 82,  // (optional, defauts to 70) minimum confidence percentage 0-99 (see UI)
		"disabled"	: 1,  // (optional)
	},
	...
] }
// ========== All "target" strings available : ==========
// "average"			: Average of all competitors
// "average_in"			: Average of all competitors in-stock
// "cheapest_n"			: Average of N cheapest competitors (or "The cheapest" if N=1)
// "cheapest_n_in"		: Average of N cheapest competitors in-stock (or ...)
// "highest_n"			: Average of N highest competitors (or ...)
// "highest_n_in"		: Average of N highest competitors in-stock (or ...)
// "cost"			: My cost
// "me_avg_n"			: My average price in last N hours (used to limit the speed at which your price changes)
// "domain:example.com"		: price of example.com (lowest if many from the same domain)
// "domainIn:example.com"	: price of example.com only if in-stock (...)
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "repricingCreate",
			"ok"	: 3,  // number of objects successfully parsed and saved (e.g. init + never[0] + never[1] = 3)
			"name"	: "The Rule Name",  // Final name in database (because we may convert your name if it contains special characters e.g.)
		},
		...
	]
}

repricingEdit - Edit an existing repricing rule

Modify an existing Repricing Rule.
See repricingCreate above to understand how these variables work.
This modifies the rule itself, not the products that are assigned to it. To assign products to rules you must use the productEdit or the productRepricing action instead.
{ "actions" : [
	...
	{
		"a"		: "repricingEdit",
		"name"		: "The Rule Name",  // case-insensitive
		"newName"	: "The Rule Renamed",  // (optional) 50 characters maximum
		"init"		: { // (optional) if present, overwrite any variable(s) of initializer fallback (see repricingCreate)
			"plusAmount"	: 1,  // for example, you could overwrite only "plusAmount" while the other variables remain the same
			// WARNING: don't forget that if the new "target" needs an N variable you must provide "var_n"
		},
		"never"		: [...], // (optional) You could overwrite the entire "never" array, but you probably want to use never_* instead.
		// This means that "never":[] erases all existing conditions. Omitting that variable is the way to keep them.
		"never_0"	: { // (optional) overwrite the first "but never" object (array index 0)
			"var_n"		: 2,  // for example, you could overwrite only "var_n" while the other variables remain the same
		},
		"never_2"	: {
			"target"	: "",  // this will disable the third "but never" condition (array index 2)
			// You don't have to worry about other variables of the object if "target" is set to "".
			// "never_2":{} does the same thing
		},
		"never_6"	: {...}, // This could be a new "but never" you add, in which case it requires all variables just like repricingCreate
		// "never_9" would be the maximum since the "never" array can take up to 10 elements
		"ignoreDomains"	: "",  // (optional) same as repricingCreate
		"charm"		: "zeros",  // (optional) ...
		"confidence"	: 82,  // (optional) ...
		"disabled"	: 1,  // (optional) 0 to enable a currently disabled rule
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "repricingEdit",
			"ok"	: 3,  // number of changes made (e.g. one var from one never_N object + another from "init" object + "charm")
			"name"	: "The Rule Name",  // Final (new) name in database (because we may convert your name if it contains special characters e.g.)
		},
		...
	]
}

repricingDelete - Delete a repricing rule

This is permanent. The repricing rule is immediately deleted from your account without any way of restoring it. This means that any products using it will no longer have any rule so their repricing will therefore fail.
{ "actions" : [
	...
	{
		"a"		: "repricingDelete",
		"name"		: "The Rule Name",  // case-insensitive
		"onlyIfUnused"	: 1,  // (optional) For safety, you can use this to generate an error if the rule is used by some product(s)
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"		: "repricingDelete",
			"ok"		: 1,
			"productsCount"	: 10,  // Number of products that were using this rule (and now have none)
		},
		...
	]
}

repricingGetAll - Retrieve all repricing rules

Please first look at repricingCreate above because the format of what this action returns has a lot in common with what you provide to repricingCreate in your request.
{ "actions" : [
	...
	{
		"a"	: "repricingGetAll",
		// This action takes no other parameter
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "repricingGetAll",
			"ok"	: 10,  // Total number of rules (length of the "rules" array)
			"rules"	: [ // array of repricing rules (alpha order) where each rule is an object :
				...
				{
					"name"		: "Some Repricing Rule",
					"created"	: 1234567890,  // Epoch time when rule was first created
					"createdBy"	: "SOMEUSER",  // Username or "API"
					"edited"	: 1234567890,  // Time last edited (if ever, otherwise variable is absent)
					"editedBy"	: "SOMEUSER",  // Username or "API" (or absent)
					"disabled"	: 1234567890,  // If the rule is currently disabled, epoch time at which it was last disabled. Otherwise absent.
					"disabledBy"	: "SOMEUSER",  // Username or "API" (or absent)
					"productsCount"	: 10,  // Number of products currently using this rule
					"ignoreDomains"	: "amazon ebay.com MyLabel1",  // domains and/or labels that are ignored from competitors (see UI)
					"charm"		: "charm",  // charm pricing mode (none|zeros|charm)
					"confidence"	: 82,  // minimum confidence percentage 0-99 (see UI)
					"init"	: { // object of initializer fallback (the first row called "Set my price to")
						"target"	: "cheapest_n",  // (see list in repricingCreate)
						"var_n"		: 3,  // if the target requires an N variable, otherwise absent
						"plusAmount"	: -2,  // positive for above, negative for below
						"plusCurrency"	: "%",  // currency (e.g. "USD") or "%"
						// So this corresponds to "Set my price 2% below average of 3 cheapest competitors"
						// WARNING: unlike the "never" objects below there is no "direction" for the initializer. It's the sign of "plusAmount" that determines below/above.
					},
					"never"	: [ // "but never" objects, executed in order
						{
							"target"	: "cheapest_n_in",
							"var_n"		: 1,
							"direction"	: "below",  // or "above"
							"plusAmount"	: -10,  // positive for plus, negative for minus
							"plusCurrency"	: "USD",  // currency or "%"
							// So this corresponds to "But never below the cheapest competitor in-stock minus 10.00 USD"
						},
						{},  // This means you left this second row of "But never" conditions empty
						{
							"target"	: "cost",
							"direction"	: "below",
							"plusAmount"	: 5,
							"plusCurrency"	: "%",
							// So this corresponds to "But never below my cost plus 5%"
						}
					],
				}
				...
			],
		},
		...
	]
}

Spreadsheets

spreadsheetDownload - Download a spreadsheet

This action is an EXCEPTION, you cannot combine it with other actions and it returns a spreadsheet file instead of a JSON response object (unless you use "savePreset" or "downloadURL" below)
This is the equivalent of whatever you can do in the Export page in your account.
Please make sure your timeout settings are high because generating a large spreadsheet may take our server some time.
MAKE IT EASY: You can click the Show API equivalent button in the Export page to generate the entire JSON call (and curl command) that corresponds to your options in the form.
{ "actions" : [
	{
		"a"		: "spreadsheetDownload",
		"preset"	: "Some Preset Name",  // (optional) Instead of providing everything below you can use one of your/our presets
			// For your own presets this should be the full name without any username before nor currency after.
			// For our default presets you can find their corresponding name by clicking the DIRECT DOWNLOAD drop-down in the export page and placing our mouse over them.
			// For example, "pricezag_backup" is our special backup preset (see "Special BACKUP format to re-import" in DIRECT DOWNLOAD drop-down)
			// You can also provide other variables below that will overwrite what your preset normally has (e.g. same preset in different currency, format, or categories).
		"savePreset"	: "My New Preset Name",  // (optional) To save everything you provide below as a new preset (which you can then use in both UI and API)
			// EXCEPTION of EXCEPTION: if you use savePreset no spreadsheet is downloaded and the response will instead be a JSON object as usual.
		"savePresetOverwrite"	: 1,  // (optional) To overwrite the existing preset provided as "savePreset" (will return an error otherwise)
			// WARNING: overwrite means nothing will remain from the previous preset, so do not attempt to only change one field.
			// To rename or delete a preset use the spreadsheetPresetEdit action below.
		"downloadURL"	: 1,  // (optional) EXCEPTION of EXCEPTION: Instead of returning the spreadsheet file it will return the usual JSON response with "fileURL" in the object,
			// which is a public URL to that file (no authentication nor API key required). The file will be available for 15 minutes for anyone to download from anywhere.
			// The "ok" value will be the number of rows and you will also see "fileSize" in bytes.
		"format"	: "xlsx|xls|csv|csv-tab",  // (defaults to csv for API)
		"currency"	: "USD",  // (MANDATORY unless preset) currency in which ALL amounts will be
		"categories"	: ["Category 1", "Category 2"],  // Only list products from these categori(es) (all products otherwise)
		"historyPeriod"	: "prev_6mo",  // (optional, defaults to "now")
			// FIRST, please click the question-marks (?) in our Export page about history periods as they explain everything about it.
			// "historyPeriod" determines how far in the past we read the amounts, before outputing this data as multiple rows per product according to "historyRow".
			// "now" : No history, only use current data (which implies no "historyRow")
			// "all" : Read the entire time period we have since you signed up (or at least for the products concerned). CAN BE SLOW.
			// "1234500000-1237800000" : read a custom time period ("from-to" epoch times)
			// "123h" : Last 123 hours. Does not snap to anything, so end will be right now and beginning can be in the middle of a clock hour.
			// "cur_123(d|wk|mo|yr)" : The last 123 day/week/month/year(s) including the current incomplete one. So "cur_2mo" downloaded on the 10th means 40 days.
			// "prev_123(d|wk|mo|yr)" : The previous finished 123 day/week/month/year(s). So "prev_2mo" downloaded on Feb 10th means the 62 days of December+January.
			// The starting time of cur_* and prev_* (and ending time of prev_*) always snap to the previous midnight time, while the end of cur_* is right now,
			// so "cur_2d" downloaded at noon covers the last 36 hours, while "prev_2d" will cover 48 hours ending 12 hours ago.
		"historyRow"	: "2w",  // (required, but only if "historyPeriod" is not "now")
			// "historyRow" determines how much time each row represents (avg of that period), therefore resulting in multiple rows per product (unless "all").
			// You can see "historyRow" as the SLICING of "historyPeriod" into individual spreadsheet rows for each product.
			// "all" : to average the entire historyPeriod as a single row per product.
			// "123(d|wk|mo|qt|yr)" : to snap at midnight to an interval of 123 calendar day/week/month/quarter/year(s). 123 can be omitted if 1 (so "wk" is fine instead of "1wk")
			// So if you download on Saturday using historyRow:"7d" each row will be the average amounts of all Saturday 00:00 to Friday 23:59 periods.
			// But if you use historyRow:"1wk" then each row will average each Monday 00:00 to Sunday 23:59 periods, and the most recent row will be the current incomplete week Mon-Sat.
			// ▶ 500,000 ROWS LIMIT: both our UI and API will refuse a history parameter that results in over 500K rows,
			// This could easily happen with historyRow:"day" if historyPeriod covers a lot of days, e.g. 365 days x 1400 products
			// If you really want this you need to download a spreadsheet per category of a couple hundred products each.
		"timezone"	: "America/Los_Angeles",  // (optional, defaults to "UTC") Format is file path of Linux folders /usr/share/zoneinfo
			// Spaces will be converted to underscores.
			// A time-zone is only useful if your history* options require some calendar-based periods (to determine when is midnight).
			// Our UI sets this to the time-zone of the current user based on their settings, but obviously the API has no such setting so it needs to be provided in the request.
		// ========== COLUMNS : ==========
		// With the column* variables you determine which columns the spreadsheet will have (see UI form in Export page for help about each of them)
		// All of them default to 0 (column won't be there) except Code.
		"columnCode"		: 1,  // (you cannot remove this one, there will always be a column with each product REF-CODE)
		"columnName"		: 1,  // Product name
		"columnCategories"	: 1,  // Categories the product is currently in ("Category 1; Category 2" in one cell)
		"columnComments"	: 1,  // Product comments
		"columnCreated"		: 1,  // Product creation date YYYY/MM/DD (in our system)
		"columnCustom"		: 1,  // The custom fields of your products (one column per unique variable among all products)
		"columnPrice"		: 1,  // Product price in the currency you requested (NOT in the product currency)
		"columnCost"		: 1,  // Product cost (in ...)
		"columnMargin"		: 1,  // Product margin percentage based on price and cost
		"columnRank"		: 1,  // My Rank
		"columnInRank"		: 1,  // My in-stock Rank
		"columnMyWebsite"	: 1,  // My Website URL
		"columnReprice"		: 1,  // Repricing Amount (in ...)
		"columnRepricingRule"	: 1,  // Repricing Rule name
		"columnRecommended"	: 1,  // Recommended price (see help in Export page)
		"columnCompUrls"	: 1,  // Competitors URLs (one per column)
		"columnCompUrlsJoin"	: 1,  // Competitors URLs (space-separated in a single cell)
		"columnCompMinMaxAvg"	: 1,  // Adds 3 columns: competitors Min/Max/Average
		"columnInCompMinMaxAvg"	: 1,  // Adds 3 columns: IN-STOCK competitors Min/Max/Average
		"columnCompPrice"	: 1,  // Adds 1 column per competitor with its price (in ...)
		"columnCompStock"	: 1,  // Adds 1 column per competitor with its stock (123|IN|OUT)
		"columnCompSales"	: 1,  // Adds 1 column per competitor with its sales/wk
		"columnCompRank"	: 1,  // Adds 1 column per competitor with its rank among all (you included)
		"columnCompInRank"	: 1,  // Adds 1 column per competitor with its rank among IN-STOCK comp. (you included)
		// ========== SECRET ADVANCED PARAMETERS : ==========
		// We also have a few unofficial options we don't really document, some of them used by our "pricezag_backup" preset for example.
		"originalCurrencies"	: 1,  // Instead of converting amounts to the currency your provide so that cells are numbers, cells will be text strings and include the original currency ("123.90 USD")
		"keepUrlLabels"		: 1,  // Normally we remove the labels of competitors URLs. This will keep them (useful for backups).
		"csvSeparator"		: ";",  // If you want the CSV format to use another separator character than comma or tab.
		"csvNoQuote"		: 1,  // Prevent CSV from ever using quotes ("). This could of course break the validity of the format.
	}
] }
// So a simple way to download a CSV spreadsheet backup would be :
// curl --connect-timeout 15 --max-time 600 --retry 2 -X POST 'https://pricezag.com/api/v3/MYAPIKEY' -d '{"actions":[{"a":"spreadsheetDownload","preset":"pricezag_backup"}]}' -o pricezag-backup-20241225.csv

spreadsheetPresetEdit - Rename or Delete a spreadsheet preset

This is the equivalent of what you can do in your account by going to the Export page, then clicking the Edit Presets button.
This is only to RENAME or DELETE a preset. To create a new preset or modify an existing one you must use the spreadsheetDownload action above.
{ "actions" : [
	{
		"a"		: "spreadsheetPresetEdit",
		"preset"	: "Some Preset Name",  // Current name of the preset
		"newName"	: "The Renamed Preset",  // To rename this preset
		"delete"	: 1,  // This will delete that "preset" from your account
		// NOTE: you cannot delete a preset that is used for auto-export (will return an error).
		// You need to first delete these scheduled auto-exports (see Export page).
	}
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "spreadsheetPresetEdit",
			"ok"	: 1,
		},
		...
	]
}

spreadsheetAutoCreate - Create a new spreadsheet auto-export

This is the equivalent of what you can do in your account by going to the Export page, then clicking the Add a new Auto-Export button.
{ "actions" : [
	{
		"a"			: "spreadsheetAutoCreate",
		"preset"		: "The Preset Name",
		"email"			: "john@example.net",
		"currency"		: "USD",  // (optional) Default is "preset" which makes us use the currency of that preset
		"attach"		: "link",  // (optional, defaults to "attach") "attach" for attached file, or "link" for hosted direct link
		"everyMonday"		: 1,  // Send the spreadsheet every Monday
		"everyTuesday"		: 1,  // ... (and so on for every day of the week until "everySunday")
		"everyMonthDay1"	: 15,  // Send every 15th of the month
		"everyMonthDay2"	: 21,  // ... and also every 21st
		"everyMonthDay3"	: "last",  // ... and also every last day of the month
			// everyMonthDay* can be from 0 to 9 for a maximum of 10 days ofo the month.
		"time"			: "3:59pm",  // "15.59" works too and would be converted to "3:59pm". "0:9", "0.09", "00:09", "0:09am" all work the same.
		"timezone"		: "America/Los_Angeles",  // (optional, defaults to "UTC") Format is file path of Linux folders /usr/share/zoneinfo
			// Spaces in city will be converted to underscores.
	}
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "spreadsheetAutoCreate",
			"ok"	: 1,
			"id"	: "5gwi6fqa",  // The reference ID of this auto-export in case you need to modify it later (see action spreadsheetAutoEdit below)
		},
		...
	]
}

spreadsheetAutoEdit - Edit a spreadsheet auto-export

This is the equivalent of what you can do in your account by going to the Export page, then clicking an Edit button in the list under AUTO-EXPORT TO EMAIL.
{ "actions" : [
	{
		"a"			: "spreadsheetAutoEdit",
		"id"			: "5gwi6fqa",  // The reference ID of the auto-export to edit.
			// You get this ID in the response of spreadsheetAutoCreate but you can also see it in the UI in the title of the pop-up that opens when you click the Edit button of an auto-export.
		"disabled"		: 1,  // (optional) 0 to enable a currently disabled auto-export
		"delete"		: 1,  // (optional) Delete this auto-export entirely
		// ===== The following (all optional) are then the same as in the spreadsheetAutoCreate action above =====
		"preset"		: "The Preset Name",
		"email"			: "john@example.net",
		"currency"		: "USD",
		"attach"		: "link",
		"everyMonday"		: 1,  // WARNING: whenever you provide at least one every* variable, all existing every* variables are erased,
		"everyMonthDay1"	: 15,  // so if you had "everyFriday"=1 before you edit, setting "everyMonthDay3" will remove that "everyFriday".
		// ... and so on (see spreadsheetAutoCreate for all every* variables)
		"time"			: "3:59pm",
		"timezone"		: "America/Los_Angeles",
	}
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "spreadsheetAutoEdit",
			"ok"	: 3,  // number of changes made
		},
		...
	]
}

Competitors & More

competitorDeleteAll - Delete a competitor from all products or categori(es)

This will mass-delete the URLs of a competitor, either from all products, or from all products of specific categori(es).
WARNING: the competitor URLs are immediately deleted from your account without any way of restoring them.
This is the same function the UI uses when you either open the page of a competitor and click Delete from all Products, or open the page of a category and click the (X) icon in the list of competitors.
{ "actions" : [
	...
	{
		"a"		: "competitorDeleteAll",
		"domain"	: "competitordomain.com",
		"categories"	: ["The Category 1", "The Category 2"],  // (optional) if not provided you must use "allProducts" = 1
		"allProducts"	: 1,  // (optional) to delete from all products ("categories" must NOT be provided)
		// So it's either "categories" or "allProducts", not both, not none.
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "competitorDeleteAll",
			"ok"	: 10,  // Number of URLs deleted
		},
		...
	]
}

mathEdit - Set/Edit the math of a competitor domain

This is the same function the UI uses when you go to Domains, click a competitor domain name in the list, then click Mathematically alter the price. Everything you can put in that form can also be done via API using this mathEdit action.
Please make sure to click the green question-mark icons in your account for details about each field.
{ "actions" : [
	...
	{
		"a"		: "mathEdit",
		"domain"	: "competitordomain.com",
		"clear"		: 1,  // (optional) To disable all math for that domain (will delete everything previously set)
		// If you set clear:1 you must not provide anything else below or it will generate an error.
		"math"	: [  // Array of math blocks to apply
			{  // This is a math block object, and all fields are optional.
				"categories"		: ["Category 1", "Category 2"],  // Only for products from these categories
				"theirPriceMin"		: 1234.90,  // Only when their price is greater or equal to N
				"theirPriceMinCurrency"	: "USD",  // Currency (can use "original" for same as website currency)
				"theirPriceMax"		: 1234.90,  // ... less or equal
				"theirPriceMaxCurrency"	: "USD",  // Currency (...)
				"themMeMin"		: -20.00,  // Only when [them-me] is greater or equal to N (see UI)
				"themMeMinCurrency"	: "USD",  // Currency or "%" of your own price (NOT "original" here)
				"themMeMax"		: -1.5,  // ... less or equal
				"themMeMaxCurrency"	: "%",  // Currency (...)
				"addPre"		: -10.00,  // Amount to add before multiplying (or remove if negative)
				"addPreCurrency"	: "USD",  // Currency (can use "original" for same as website currency)
				"multiply"		: 1.1,  // Multiply by this factor
				"divide"		: 1.1,  // OR divide (do not provide both)
				"addPost"		: 5.00,  // Amount to add after multiplying (or remove if negative)
				"addPostCurrency"	: "USD",  // Currency (...)
				// Seems overwhelming? Check out our simple example below the response.
			},
			... // Up to 10 blocks total (math will be applied in order)
		],
		// NOTE: We do not currently provide a way to only modify a single variable or math block. If you provide the math array it will replace everything that might be there.
		// NOTE: Any math block that neither multiplies nor divides (unset or 1) and doesn't have addPre!=0 or addPost!=0 will be ignored,
		// and if all of them	are ignored then it will have the same effect as deleting all math with clear:1
		// In other words your math have to actually do something to be saved.
		// TRICK: if you ever need to temporarily disable the math of a domain without deleting everything,
		// you can set categories : ["Some Empty Existing Category"]
	},
	...
] }
RESPONSE:
{
	...
	"actions" : [
		...
		{
			"a"	: "mathEdit",
			"ok"	: 3,  // number of math blocks changed (removing/clearing is a change)
		},
		...
	]
}
SIMPLE EXAMPLE — Remove a 10% tax all over amazon.co.uk :
{ "actions" : [
	{
		"a"		: "mathEdit",
		"domain"	: "amazon.co.uk",
		"math"		: [
			{
				"divide"	: 1.1
			}
		]
	}
] }

More examples

These requests are POST data sent to https://pricezag.com/api/v3/MYAPIKEY
▶ EXAMPLE 1 — Update the cost of a product
REQUEST:
{
	"actions" : [
		{
			"a"		: "productEdit",
			"code"		: "SKU-1234",
			"cost"		: 140.50,
			"costCurrency"	: "EUR"
		}
	]
}
RESPONSE:
{
	"ok"		: 1,  // would return 0 if SKU-1234 was already at that cost
	"errors"	: 0,  // would return 1 if SKU-1234 didn't exist
	"actionsCount"	: 1,
	"actions" : [
		{
			"a"	: "productEdit",
			"ok"	: 1  // would return 0 if SKU-1234 was already at that cost, and -1 if it didn't exist
		}
	]
}

▶ EXAMPLE 2 — Read a product that does not exist
REQUEST:
{
	"actions" : [
		{
			"a"		: "productGet",
			"code"		: "NON-EXISTING-SKU",
			"currency"	: "USD"
		}
}
RESPONSE:
{
	"ok"		: 0,  // because no action returned ok>0
	"errors"	: 1,  // because 1 action returned ok<0
	"actionsCount"	: 1,
	"actions" : [
		{
			"a"	: "productGet",
			"ok"	: -1,  // this is how you know it failed
			"error"	: "Sorry, the product #NON-EXISTING-SKU does not exist. Maybe someone else just removed or renamed it?"
		}
	]
}

▶ EXAMPLE 3 — Create a category, a repricing rule, then two products assigned to them
REQUEST:
{
	"actions" : [
		{
			"a"		: "categoryCreate",
			"name"		: "My New Category"
		},
		{
			"a"		: "repricingCreate",
			"name"		: "My New Repricing Rule",
			"init"	: {
				"target"	: "cheapest_n",
				"var_n"		: 2,
				"plusAmount"	: 0,
				"plusCurrency"	: "%"
				// So this corresponds to "Set my price at the mid-point between the 2 cheapest competitors"
			},
			"never"	: [
				{
					"target"	: "cost",
					"direction"	: "below",
					"plusAmount"	: 5,
					"plusCurrency"	: "%"
					// So this corresponds to "But never below My Cost + 5%"
				}
			],
			"charm"		: "charm"
		},
		{
			"a"		: "productCreate",
			"code"		: "SKU-1001",
			"name"		: "My New Product 1",
			"price"		: 199.90,
			"currency"	: "USD",
			"cost"		: 112.00,
			"costCurrency"	: "USD",
			"repricingRule"	: "My New Repricing Rule",  // same rule name we just created
			"categories"	: ["My New Category"],	// same category name we just created
			// ... although productCreate would create non-exsting categories anyway, so the categoryCreate action was unnecessary
			"competitors"	: ["https://...", "https://..."]
		},
		{
			"a"		: "productCreate",
			"code"		: "SKU-1002",
			"name"		: "My New Product 2",
			"price"		: 59.95,
			"currency"	: "USD",
			"cost"		: 31.50,
			"costCurrency"	: "USD",
			"repricingRule"	: "My New Repricing Rule",
			"categories"	: ["My New Category"],
			"competitors"	: ["https://...", "https://..."]
		}
	]
}
RESPONSE:
{
	"ok"		: 4,  // because all 4 actions returned ok>0
	"errors"	: 0,  // because no action returned ok<0
	"actionsCount"	: 4,
	"actions" : [
		{
			"a"	: "categoryCreate",
			"ok"	: 1
		},
		{
			"a"	: "repricingCreate",
			"ok"	: 1
		},
		{
			"a"	: "productCreate",
			"ok"	: 1
		},
		{
			"a"	: "productCreate",
			"ok"	: 1
		}
	]
}

▶ EXAMPLE 4 — Merge two categories into another while removing these two original categories
REQUEST:
{
	"actions" : [
		{
			"a"			: "categoryCopy",
			"categoriesFrom"	: ["Original Category 1", "Original Category 2"],
			"deleteMode"		: "cat",
			// Note that "removeMode":"cat" is unnecessary because deleteMode will delete these two categories anyway
			"categoriesTo"		: ["A New Category"]
		}
}
RESPONSE:
{
	"ok"		: 1,  // because one action returned ok>0
	"errors"	: 0,  // because no action returned ok<0
	"actionsCount"	: 1,
	"actions" : [
		{
			"a"			: "categoryCopy",
			"ok"			: 3,  // because 3 categories were altered, the two original were deleted and one destination was created
			"categoriesCreated"	: 1,
			"categoriesDeleted"	: 2,
			"categoriesModified"	: 0,
			"productsCount"		: 123
		}
	]
}

▶ EXAMPLE 5 — Changing the N variable of an alert rule and its direction
REQUEST:
{
	"actions" : [
		{
			"a"		: "alertRuleEdit",
			"email"		: "john@example.net",
			"ruleNumber"	: 3,
			"rule"		: {
				"var_n"		: 1.5,
				"direction"	: "drop"
			}
		}
}
RESPONSE:
{
	"ok"		: 1,  // because one action returned ok>0
	"errors"	: 0,  // because no action returned ok<0
	"actionsCount"	: 1,
	"actions" : [
		{
			"a"	: "alertRuleEdit",
			"ok"	: 2  // because 2 variables were successfully modified
		}
	]
}