Skip to content

Epinium API ​

Access: Settings → API Keys

Epinium exposes your data through a REST API so you can read it from your own tools: a custom dashboard, an analysis script or an AI assistant. It is the same information you see in the app, with the same figures.

The API reads your data and writes to Amazon and to your stores: it queries catalogue, campaigns and metrics, and what is inside your Shopify and WooCommerce stores; it creates and edits Sponsored Products campaigns and edits Sponsored Brands and Display ones; it creates and edits portfolios; it creates, edits, clones and runs workflows; it creates, resolves and applies tasks; and it creates, edits and deletes products and content in your connected Shopify and WooCommerce stores. Your Amazon catalogue only changes when an approved task is applied; your stores' catalogue changes directly, with no task. Each area has its own permission, and you grant the write ones when you create the key.

Looking to use it from an AI assistant?

You do not need to write any code. The Epinium MCP connects this same data to Claude and other compatible assistants.

Generate a token ​

  1. Go to Settings → API Keys
  2. Click Create API key and give it a name that reminds you what it is for
  3. Tick the permissions it needs
  4. Copy the key and store it somewhere safe
  5. Use it in the Authorization header of every request

The key is shown only once

The full value appears a single time when you create it. If you lose it there is no way to recover it: you have to revoke it and create another.

Permissions ​

Every token carries the permissions you tick when creating it. Grant only the ones your integration needs.

PermissionGives access to
catalog:readProducts, brands, countries and clusters, with their metrics: sales, Amazon fees and margin, returns, stock and, for Vendor, reviews
campaigns:readCampaigns, portfolios, ad groups, targets, product ads and search terms
workflows:readYour workflows, their configuration and which campaigns they reach
connections:readYour account's connections: Amazon, Shopify and WooCommerce
tasks:readEpinium tasks and their items
skills:readEpinium's catalog of validated marketing playbooks
platform:readWhat is inside your Shopify and WooCommerce stores: products, stock, prices and the rest of what each store exposes, read live from its own API. It includes the record of the changes made in them (/v1/platform-changes, 1 credit per row returned)
catalog:writeCreate, edit and delete keyword clusters: their name, their positive and negative keywords and the products they group. Nothing reaches Amazon
campaigns:writeCreate and change campaigns, portfolios, ad groups, targets and product ads on Amazon, and configure what the optimizer chases on each one
platform:writeCreate, edit and delete products and content in your connected Shopify and WooCommerce stores, directly and with no review first, through the same route used to query them (POST /v1/platform-request): a mutation in Shopify, or a non-GET method in WooCommerce, with the Idempotency-Key header. It also needs platform:read (the Edit level on the API keys screen grants both). Every change is recorded for 12 months and costs the same as a query: 5 credits per call plus 1 per KB returned
tasks:writeCreate tasks, approve or reject their suggestions, close them and apply what was approved
workflows:writeCreate, copy, edit, switch on, pause, delete and restore workflows, and run or simulate them

Writing is for the Business and Master plans. On Free and Guru tokens read: you cannot create a key or authorise an assistant with write permissions, and if you move down to one of those plans, the ones you already had keep reading but their writes are refused with a 403.

A token reaches all the connections in your account. If you are an agency, it reaches the connections each client shared with you when accepting your invitation.

If you are an agency: pick the account on every call ​

An agency token reaches several accounts, so every request has to say which one it operates on. Use the Epinium-Account: <account id> header, or the account_id parameter if you are calling through the MCP.

/v1/me and /v1/accounts are the exception: they answer without picking an account, and they are exactly the ones that tell you which accounts you reach and what they are called.

Once the account is picked, you see all of it: its products and campaigns are scoped to the connections it shared with you, and its account-level resources — workflows, tasks and clusters, which hang off no connection — are scoped to the account. Anything outside your reach is not filtered out silently: an account_id you were not invited to returns an explicit error, not an empty list.

What endpoints it covers ​

Every resource has a list endpoint, and most also let you retrieve a single item by its id. The metrics resources only have a list: an aggregated row over a date range has no identifier of its own.

Catalog — catalog:read ​

EndpointWhat it returns
/v1/productsYour unified products
/v1/amazon-seller-productsThe Seller Central view of each product, and the category of its rank with expand=salesRanks
/v1/amazon-vendor-productsThe Vendor Central view, the category of the rank with expand=salesRanks and the reviews with expand=customerFeedback
/v1/amazon-advertising-productsThe Advertising view
/v1/seller-product-metricsSales, sessions, Buy Box, rank, Amazon fees, margin, returns, stock, B2B and days of coverage per product
/v1/vendor-product-metricsVendor sales per product, with manufacturing and sourcing, returns, sell-in with Amazon, inventory and days of coverage
/v1/product-brandsYour brands
/v1/countriesCountries and marketplaces
/v1/clustersYour keyword clusters; with catalog:write you can also create and edit them

Advertising — campaigns:read ​

EndpointWhat it returns
/v1/campaignsYour Amazon Advertising campaigns
/v1/campaign-metricsSpend, sales, ACOS and ROAS per campaign
/v1/portfoliosYour portfolios and the budget cap each one puts on its campaigns
/v1/adgroupsThe ad groups of each campaign
/v1/adgroup-metricsPerformance per ad group
/v1/targetsKeywords and product targets
/v1/target-metricsPerformance per target, with its keyword and match type
/v1/product-adsThe link between an ad group and the product it advertises
/v1/product-ad-metricsPerformance per advertised product, with its ASIN
/v1/search-termsThe actual shopper queries
/v1/search-term-metricsPerformance per query, with the text and the target that matched it
/v1/campaigns/{id}/optimization-historyWhat the automation changed on a campaign, with the net effect
/v1/advertising-changesEvery change Epinium has pushed to Amazon, whatever set it off

Automation — workflows:read ​

EndpointWhat it returns
/v1/workflowsYour workflows, with their schedule and whether they are active
/v1/workflows/{id}The full configuration of one: its diagram and every variable with its effective value
/v1/workflows/{id}/campaignsWhich campaigns it actually reaches, and why each one is in
/v1/workflows/{id}/performanceThe performance of those campaigns, with their target next to it so you can judge it

A workflow can be scheduled every night and still touch no campaign at all: /campaigns is what surfaces that, and when the set comes back empty it names the reason.

Connections, tasks and skills ​

EndpointPermissionWhat it returns
/v1/connectionsconnections:readYour connections: Amazon, Shopify and WooCommerce
POST /v1/platform-requestplatform:read, and platform:write to writeA query to one of your stores or, with platform:write, a change to it: GraphQL on Shopify, REST on WooCommerce
/v1/platform-request/docs/{platform}platform:readThat platform's API version, its reference and the fields your store has right now
/v1/taskstasks:readEpinium tasks
/v1/task-itemstasks:readThe items of each task
/v1/skillsskills:readEpinium's catalog of marketing playbooks

The skills catalog is content written by Epinium, not data from your account: it is the same for every client. Which parts of each playbook you receive depends on your plan.

Queries to your stores return no personal data: names, emails, phones and addresses are refused before the request reaches the store. They are charged by the size of the response, and so are changes. These two routes are not in the browsable reference: the usual way to use them is through the Epinium MCP.

There is also /v1/me, which tells you which permissions and which accounts your token reaches. It is the first useful call to check that the key works.

Three rules when reading the data ​

These three explain almost every question about interpreting the response:

  • Monetary amounts are text, not numbers. "cost": "8.22" arrives as a string on purpose, so it does not lose precision when converted to a binary decimal.
  • Ratios come as fractions, not percentages. An ACOS of 90.83% arrives as 0.908287. Multiply by 100 to display it.
  • null is not 0. null means Amazon did not report that figure; 0 means an actual zero. An acos of null is a campaign that spent without selling, not free advertising. And new_to_brand_sales arrives as null on Sponsored Products because Amazon does not measure it for that format.

Profitability, stock and reviews of your products ​

Product metrics go beyond sales: they also tell you how much Amazon keeps, how much stock is left and what buyers think. Four things before drawing conclusions:

  • Seller fees and margin exclude VAT, while sales includes it where it applies: do not subtract one from the other. margin_before_ads is what is left after paying Amazon, before advertising and product cost: for the margin after ads, subtract the spend from /v1/product-ad-metrics. Amazon books storage on a single day of the month and refunds on the refund date, so read the margin by month, not by day.
  • days_of_coverage is how many days the stock lasts at the pace of the window: the stock of the last day divided by the average daily units of the range you ask for. It is null with any granularity other than total, when the product sold nothing or, when grouping, if group_by does not include the country. With order_by=days_of_coverage&order=asc you get first the products about to run out.
  • Vendor also has the sell-in: the units you confirmed to Amazon, those in open purchase orders, the fill rate and the lead time, plus returns split by manufacturing and sourcing and the health of the inventory.
  • The category of the rank and the reviews are requested on the product, not on the metrics. expand=salesRanks tells you which category each Best Seller Rank position belongs to, and on Vendor expand=customerFeedback returns the positive and negative review topics and the return reasons of the category. It is this week's snapshot, with the six-month trend Amazon calculates, not a history.

Account totals, by period and by cluster ​

To know how much the account sold in September you do not need to page through the catalog and add it up: the seven metrics resources aggregate on their own.

  • group_by adds up across entities. account gives the account total; country and connection break it down by country or by connection, and they combine by repeating the parameter (group_by[]=connection&group_by[]=country). account does not combine with anything. Rows are still split by currency, since amounts are never converted, and, in advertising, by ad type (SP, SB, SD).
  • granularity splits the range into calendar periods. Besides total and daily it accepts weekly, monthly, quarterly and yearly, and each row carries in date the start of its period: the Monday of the week, or the 1st of the month, quarter or year. The periods at either end are partial: from January 15 to March 10 with monthly returns January from the 15th to the 31st, all of February, and March from the 1st to the 10th.
  • cluster narrows it down to a product family. On Seller, Vendor and product ad metrics, cluster=<id> keeps only the products in that cluster; with several ids, their union. It combines with the above: group_by=account&granularity=monthly&cluster=<id> is the monthly series for that family.
  • group_by=cluster gives one row per cluster. On the same three metrics, group_by=cluster returns the figures of each of your clusters in a single call, with its id in cluster and its name in cluster_name. A product in several clusters counts in full in each of them, so the rows do not add up to the account total, and products in no cluster do not appear. It combines with country and connection, and with the cluster filter to choose which clusters come back; if your clusters add up to more than 2,000 product-cluster memberships, the API asks you to narrow them down with that filter.

In grouped rows, the fields that identify an entity (product, asin, campaign…) arrive as null, and so does whatever cannot be added up: the rank, the price, the channel and the Vendor rates. Seller stock is only added up if you group by country: with FBA's pan-European inventory, Amazon reports the same stock in every marketplace, and adding it up across countries would count the same units several times. That is why, with group_by=account or connection, stock and days_of_coverage arrive as null.

A cluster groups catalog products, not loose ASINs: the same ASIN on another connection is left out unless its product is in the cluster too. And rows Amazon could not match to a catalog product do not enter the filter: around 5% of Vendor sales and 3% of product ad spend.

How much it saves in credits is explained in Credits.

What the automation changed on a campaign ​

/v1/campaigns/{id}/optimization-history answers what the workflow changed on a campaign, not what it decided. The difference matters: in a decision log the same target can show up with a raise and a cut on the same night, and there you cannot see that they cancel each other out.

So every row comes aggregated to the entity's net effect: the bid from before the first change of the period against the bid after the last one, plus how many times it moved along the way. A target raised from €0.18 to €0.45 and cut back to €0.18 that same night comes back with a net of zero and two moves — which is the useful reading, and exactly the one a flat list hides.

Three things bound what it returns:

  • Real changes only. Simulations are not exposed here, under any parameter: they are bids that never reached Amazon.
  • Only what was applied, not what was proposed. The optimizer computes a bid that a ceiling can then cut; only what was sent is reported.
  • A 90-day window at most. That is how long these records are kept. With no dates, it returns the last 90 days.

Changes that are not bids — harvested keywords, negatives, placement adjustments, quarantined or blocked targets — are counted separately in other_changes, because they have no previous bid to compare against.

Everything Epinium has changed on Amazon ​

/v1/advertising-changes answers a different question from the previous one: not why the automation decided something, but what was touched and whether Amazon accepted it. And not only what the automation does: also what a person did from the app, an internal process, or this very API.

The rule is one line: if it was sent to Amazon, there is a row. Three consequences follow, and they are worth having clear:

  • What Amazon rejected leaves a row too, with result: "failed" and the Amazon message in error. It is the first place to look when a change did not take effect.
  • What was never attempted leaves no row: a budget below the country minimum, or a bid that already was the one being asked for.
  • Configuration changes inside Epinium stay out, because they never reach Amazon.

Every row says where it came from. source tells the app (front), the optimizer (workflow), an internal process (system) and this channel (public-api, mcp) apart, and actor identifies the person or the specific credential — when someone made the change from the app, their email is there. If it came from a workflow, correlation_id is that run's identifier, which is the key for cross-referencing this response with optimization-history.

You can filter by campaign, adgroup, entity, entity_type, source, result, field and correlation_id, and narrow the window with start_date and end_date (YYYY-MM-DD, both inclusive). Records are kept for twelve months.

Writing to Amazon ​

With the campaigns:write permission the API stops being read-only: it creates campaigns, ad groups, targets and product ads, and moves their states, budgets and bids. What you write really goes to Amazon and leaves its row in /v1/advertising-changes.

Five things worth knowing before your first call:

  • Everything is born paused. A campaign or an ad group is created PAUSED unless you ask for ENABLED explicitly, because anything enabled starts spending as soon as Amazon accepts it.
  • Archiving has its own verb: DELETE /v1/<resource>/{id}. It is the only irreversible operation in this API: on Amazon, archiving cannot be undone. That is why it is not a state value you could set by accident next to a bid change, but a separate call with a separate verb. If you only want to stop the spend, use state: "PAUSED", which is reversible.
  • Every write requires an Idempotency-Key header. Reuse the same value if you retry the same intent: without it, a network timeout becomes two campaigns.
  • A batch is not all-or-nothing. POST /v1/<resource>/batch takes up to 1000 entities and answers with one row per entity, in the order you sent them. applied is what reached Amazon, and it is the only thing billed. A partial result is the normal outcome, not an error: read it instead of retrying the whole batch, because a retry would duplicate what already went in.
  • An entity counts as applied only if everything you asked for went through. If a PATCH changes state and budget and Amazon accepts the first and rejects the second, that entity comes back failed with the reason — calling it applied is how you end up believing a bid moved when it did not.
RouteWhat it does
POST /v1/campaignsCreate a campaign
POST /v1/campaigns/fullCampaign + ad group + targets + product ads in one call
PATCH /v1/campaigns/{id} · POST /v1/campaigns/batchState, daily budget, name and portfolio (the portfolio, Sponsored Products only)
POST /v1/adgroups · PATCH /v1/adgroups/{id} · POST /v1/adgroups/batchCreate; state, default bid and name
POST /v1/targets · PATCH /v1/targets/{id} · POST /v1/targets/batchCreate targets (the types depend on the ad group's ad product); state and bid
POST /v1/product-ads · PATCH /v1/product-ads/{id} · POST /v1/product-ads/batchAdvertise products; state
POST /v1/portfolios · PATCH /v1/portfolios/{id}Create a portfolio; name, state and budget cap
DELETE /v1/campaigns/{id} · DELETE /v1/adgroups/{id} · DELETE /v1/targets/{id} · DELETE /v1/product-ads/{id}Archive - irreversible

What cannot be changed, and why: what defines a target — its keyword, its ASIN, its match type — is not editable on Amazon, so to change it you create another and archive the previous one. A product ad only takes state: it is the link between a product and an ad group, with no bid or name of its own. And entities managed by the optimizer are refused, because its 03:00 UTC run would write over you.

A portfolio's cap limits what all its campaigns spend together. MONTHLY_RECURRING resets every month, DATE_RANGE applies between startDate and endDate and NO_CAP removes it; the currency is the marketplace one and you do not send it. In a PATCH, budget is replaced whole: send the full cap you want, not just the amount. To move a Sponsored Products campaign into or out of a portfolio, change its portfolioId with PATCH /v1/campaigns/{id}.

Two status codes worth telling apart. An entity that does not exist, or belongs to another account, answers 404 — never 403, which would confirm it exists. A rejection from Amazon answers 200 with result: "failed" and the reason: the call did arrive, and what failed was the change.

All three ad products can be written, but not in the same way:

  • Sponsored Brands and Sponsored Display are changed and archived with the same routes as Sponsored Products: state, budget, name and bids of campaigns, ad groups, targets and product ads. What you cannot do yet is create them: POST /v1/campaigns, /v1/campaigns/full, /v1/adgroups and /v1/product-ads are Sponsored Products only.
  • Sponsored Brands ad groups have no default bid. A defaultBid change on one is rejected with a message that says so. Sponsored Display ad groups do have one.
  • Targets and negative targets can be created in SB and SD ad groups, with POST /v1/targets. The accepted targetType depends on the ad group's ad product (table below). Ad-group level only: SB and SD have no campaign-level negative targets.
  • Epinium checks the marketplace bid and budget limits only for Sponsored Products, before sending. For Brands and Display it applies no limits of its own: Amazon validates, and its reason comes back in the response, per entity with result: "failed" or, when creating targets, in failure_details, where reference identifies the target (keyword, ASIN, SKU or the audience, category or location id).
Ad productAccepted targetType
Sponsored ProductsKEYWORD, PRODUCT, PRODUCT_CATEGORY
Sponsored BrandsThose of Sponsored Products plus THEME (themeMatchType KEYWORDS_RELATED_TO_YOUR_BRAND or KEYWORDS_RELATED_TO_YOUR_LANDING_PAGES)
Sponsored DisplayKEYWORD; PRODUCT by ASIN or by sku, with productMatchType PRODUCT_EXACT or PRODUCT_SIMILAR; PRODUCT_CATEGORY with refinements (brand, price, rating, age range, Prime eligibility); THEME (INTERESTED_AUDIENCE); PRODUCT_AUDIENCE (ASIN + event PURCHASE or VIEW + lookback of 7, 14, 30, 60, 90, 180 or 365 days); AUDIENCE (audienceId), CONTENT_CATEGORY (contentCategoryId) and LOCATION (locationId)

The ids for AUDIENCE, CONTENT_CATEGORY and LOCATION come from the Amazon Ads console: the API does not list them yet.

Optimization config ​

With the same campaigns:write permission, besides writing to Amazon you can read and change the configuration Epinium's optimizer uses: what it chases on each campaign. This is a different surface from the one above, and worth not confusing with it.

It never reaches Amazon. It is Epinium's own configuration, consumed by the optimizer on its nightly run, not a fact about the Amazon account. That is why two rules from "Writing to Amazon" do not apply here: no Idempotency-Key header is needed - there is no round trip to Amazon a retry could duplicate - and the change leaves no row in /v1/advertising-changes, which only records what was sent to Amazon.

It is read with expand, not by default. GET /v1/campaigns and GET /v1/campaigns/{id} only return optimizationConfig if you ask for ?expand[]=optimizationConfig. It is opt-in because very few campaigns have this configuration saved: without the expand, the column would come back null almost every time.

What you can set with PATCH /v1/campaigns/{id}/optimization-config, in business terms:

  • The objective: a target ACOS or ROAS, or a volume objective - impressions or orders - with an ACOS ceiling alongside it.
  • The bid limits: the floor and ceiling it cannot cross, the placement multiplier cap, and the minimum spend before negativizing a target.
  • The monthly budget the optimizer chases - not the campaign's Amazon daily budget, which is changed with PATCH /v1/campaigns/{id} and is a different field with the same name.
  • The campaign's harvesting overrides: its discovery level and whether it allows targeting competitor ASINs.
  • Which workflows optimize it.

That last point is the one that really matters: assigning workflows is what puts the campaign under optimization, not any other field in this block. linkedWorkflowDefinitionIds force-includes it - the workflow processes it every night even if its own filter would not have picked it - and it is how a workflow moves real bids on Amazon; confirm it with the person before writing it, exactly as you would turning on an automation. A workflow with auto-selection can also pick it up on its own, and workflowsOptOut: true is what declines that automatic selection. The only thing that reliably takes it out is excludedWorkflowDefinitionIds: it always wins, whatever else says otherwise.

aiEnabled is not the optimizer's switch

It is the account holder's consent flag, and it travels to Amazon as the epinium:ai_enabled tag. Setting it to false does not take the campaign out of optimization: that is what excludedWorkflowDefinitionIds is for, or removing it from linkedWorkflowDefinitionIds.

optimizeDate and optimizingSince are read-only - the optimizer itself stamps them - and writing either one gets you a 400.

POST /v1/campaigns/optimization-config/batch applies the same patch to up to 1000 campaigns, and it is not all-or-nothing: it answers with one row per campaign, in the order you sent them, and each one is validated against its own stored state - a ceiling that is mandatory for one objective can be optional for another - so one row can fail without affecting the rest.

Two status codes worth telling apart, just as when writing to Amazon: a validation rejection - an objective out of range, a workflow that is not yours - answers 200 with result: "failed" and the reason in reason. A campaign that does not exist or belongs to another account answers 404 on the single PATCH; in the batch, that same campaign instead comes back as a failed row with reason: "not_found", precisely so one bad row cannot sink the other 999.

RouteWhat it does
PATCH /v1/campaigns/{id}/optimization-configChange a campaign's optimization config
POST /v1/campaigns/optimization-config/batchThe same config on up to 1000 campaigns, one row per result

Writing tasks ​

With the tasks:write permission the API creates tasks and decides on their suggestions, just as you do in Processes → Tasks. It is the piece that lets an assistant or an integration propose changes and a person review them before they reach Amazon.

Four things worth knowing before your first call:

  • Approving does not apply. There are two steps: approve or reject each suggestion, and then POST /v1/tasks/{id}/apply. At that point campaign changes — daily budget, state and name — go to Amazon straight away, and the response says which ones went in (applied) and which ones it rejected (failed). Product changes are queued (enqueued) and applied in the background: to find out whether they went in, read the suggestions again and look at appliedAt and applyError.
  • A suggestion that nothing knows how to apply is a note. Today, product suggestions and campaign suggestions that carry a fieldPath are applied; those without one, and those for ad groups, targets, product ads and search terms, stay as notes. Approving them records the decision and changes nothing, and the response says so with autoApplySkipped: true. It is not an error. When creating, on the other hand, a campaign suggestion with a field other than dailyBudget, state or name, or with a value that cannot be applied, is rejected with a 400: saving it would promise a change nobody is going to make.
  • Suggestions come with the task. Up to 50 in items when you create it, each pointing to an entity in your account; they cannot be added later. An entity that does not exist or belongs to another account rejects the whole task, without creating anything.
  • Resolving a task is final. Its pending suggestions are not resolved with it: they stay frozen and can no longer be approved. Decide the ones that matter to you first and close the task afterwards. Resolving it does not resume any workflow either.
RouteWhat it does
POST /v1/tasksCreate a task, with its suggestions
PATCH /v1/tasks/{id}Title, description, priority and expiry date
POST /v1/tasks/{id}/resolveResolve or discard it
PATCH /v1/task-items/{id} · POST /v1/task-items/batchApprove or reject suggestions, optionally with your own value in appliedValue
POST /v1/tasks/{id}/applyApply what was approved

As with Amazon, every write requires Idempotency-Key, and the suggestions batch is not all-or-nothing: it answers with one row per suggestion and applied counts the ones that were recorded. Creating, editing or resolving a task costs a flat charge per call; approving or rejecting is billed per recorded suggestion. Applying is not billed separately: each suggestion was already billed when it was approved.

Writing keyword clusters ​

With the catalog:write permission the API creates and edits your keyword clusters, the same ones you manage in Segmentation → Clusters: a named group of products with the positive keywords they should rank or advertise for and the negative keywords to keep away from. It is the way for an assistant that has found a keyword gap to leave it saved where Epinium's SEO prompts, the optimizer's keyword discovery and the cluster filter of the product charts will pick it up.

Four things worth knowing before your first call:

  • Nothing reaches Amazon. A cluster lives in Epinium. That is why these writes leave no row in /v1/advertising-changes, and why your AI client will not ask you to confirm them.
  • Editing is by deltas, not by replacement. PATCH /v1/clusters/{id} takes addPositiveKeywords, removePositiveKeywords, addNegativeKeywords, removeNegativeKeywords, addProducts, removeProducts and name; send only what changes. Adding a keyword that is already there updates its language, score and purpose, matched by text; removing one that is not there does nothing. A cluster holds up to 100 positive and 50 negative keywords, counted after the change, and a keyword cannot be positive and negative at once.
  • Keywords follow the app's rules. Lowercased, up to 80 characters, Amazon's character set, no leading - or . and no trailing -, + or .; duplicates are merged. language is a code like es-ES or en-GB, epiniumScore goes from 1 to 5 and purpose is SEO, PPC or both (both by default).
  • A protected cluster is refused with 409. Protecting a cluster from the app is a person's decision, and this API cannot undo it: unprotect it in Segmentation → Clusters first. The protected field in the response tells you before you try.
RouteWhat it does
POST /v1/clustersCreate a cluster, with its keywords and products if you have them
PATCH /v1/clusters/{id}Add or remove keywords and products, or rename it
DELETE /v1/clusters/{id}Delete it, with no way back

Every product you link must belong to your account — for an agency, to the connections the account shared with you — and be a catalog product (type seller or vendor in /v1/products; advertising products are refused), or the whole call is refused with a 400 naming the offending ids, and nothing is written. The cluster is owned by the person behind the credential, so it shows up in the app's table under their name. Deleting is final - there is no bin - and is refused with a 409 while the cluster is protected or a smart campaign uses it as its base cluster; the error names those smart campaigns, which have to be changed or deleted first. Each write costs a flat charge per call, and the plan's cluster quota applies here as it does in the app.

Limits ​

  • Metrics require a date range. start_date and end_date are mandatory on every metrics resource.
  • The range has a maximum, and it depends on the granularity. With granularity=daily, 93 days; with total or with the calendar periods (weekly, monthly, quarterly, yearly), 366. On targets and search terms, the heaviest resources, those drop to 31 and 93. Asking for more returns an error naming the exact limit. Both dates are included in the range.
  • Lists paginate in two different ways. Catalog and structure resources use a cursor (starting_after); metrics resources use limit and offset. In both cases has_more tells you whether another page follows.
  • There is a request limit per token. If you exceed it you get a 429 and simply need to retry more slowly.
  • There is also a limit on concurrent requests. A 429 can mean "too many at once", not just "too many per minute"; a 503 means the server is momentarily at capacity. Both carry Retry-After and both are transient: retry with fewer requests in parallel.
  • A single response cannot expand more than 5,000 objects. expand over lists — a cluster's products, a connection's countries — multiplies at every level. Going over returns a 400 naming how many objects it would have returned: lower limit, drop an expand, or fetch that data from its own endpoint.

Credits ​

Calls to this API consume credits from your account. What each resource costs is visible in the app, under Settings → Credits.

Every response tells you what you spent:

HeaderWhat it is
X-Epinium-Credits-CostWhat this call cost
X-Epinium-Credits-RemainingWhat you have left. It is approximate: if you have other things consuming at the same time, it can drift
X-Epinium-Credits-BreakdownThe breakdown, per resource and unit price: product=100x1,country=100x0
X-Epinium-Credits-MaxThe most it could have cost, computed before running it

Three things worth knowing:

  • You are only charged for what is delivered. A call that fails costs nothing, and an expand that returns no data — because your token lacks that scope, or the record belongs to another account — costs nothing either.
  • In metrics, the date range costs too, not just the rows. Asking for a year with limit=1 is not cheap: on top of every row returned you pay for every day of the range, because that is what has to be read to answer, with a 3-day minimum per call. To spend less, shorten the range.
  • For a total, group_by instead of paging. An account total is only a few rows, one per currency, whereas paging through the catalog to add it up pays for every product on every page. On a real account, a month's total sales cost 157 credits with group_by=account and about 17,600 by paging.

If your credits do not cover it you get a 402 before the call runs: you will never be left with half a response. And it does not just say no, it says what to change and to what value:

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "insufficient_credits",
    "message": "insufficient credits: needs up to 366 (campaignMetrics=1x1,campaignMetrics:window=365x1), 120 available. Shorten the date range to 119 days or less.",
    "param": "end_date"
  }
}

param is the same remedy in machine-readable form: limit, end_date or include_total. When it is absent, no parameter change fits the request and you need to top up.

Parameters and responses of each endpoint ​

The table above says which resources exist. To see the input parameters and the exact shape of each response, there are two routes:

  • Browsable reference — every endpoint with its parameters, their types and an example response. Generated from the API itself, so it cannot fall out of date. In English.
  • OpenAPI schema — the raw OpenAPI 3.0 document, to import into Postman, Insomnia or a client generator. No token needed.

Epinium Documentation