Developers › Docs › API reference › Publish or reprice
Publish or reprice
Per target, FLUF creates a listing if the item isn't listed there, reprices it if it is, or does nothing if nothing needs changing.
The product must already exist, either in FLUF or in a connected source store. A source-store vid that FLUF hasn't seen yet is imported on the first publish. This call can't create a product from raw fields or change its title, description or photos. See Where products come from.
curl -X POST "https://fluf.io/wp-json/fc/api/v1/items" \
-H "Authorization: Bearer fluf_pat_your_token" \
-H "Content-Type: application/json" \
-d '{
"vid": "shopify_8123456789012_0",
"targets": [
{ "account": "ebay:718", "price": 45.00 },
{ "account": "vinted:22", "price": 40.00 }
]
}'const res = await fetch("https://fluf.io/wp-json/fc/api/v1/items", {
method: "POST",
headers: {
"Authorization": "Bearer fluf_pat_your_token",
"Content-Type": "application/json",
},
body: JSON.stringify({
"vid": "shopify_8123456789012_0",
"targets": [
{
"account": "ebay:718",
"price": 45.0
},
{
"account": "vinted:22",
"price": 40.0
}
]
}),
});
const data = await res.json();import requests
res = requests.post(
"https://fluf.io/wp-json/fc/api/v1/items",
headers={
"Authorization": "Bearer fluf_pat_your_token",
},
json={
"vid": "shopify_8123456789012_0",
"targets": [
{"account": "ebay:718", "price": 45.0},
{"account": "vinted:22", "price": 40.0},
],
},
)
data = res.json()<?php
// composer require guzzlehttp/guzzle
$client = new GuzzleHttp\Client();
$res = $client->request('POST', 'https://fluf.io/wp-json/fc/api/v1/items', [
'headers' => [
'Authorization' => 'Bearer fluf_pat_your_token',
],
'json' => [
'vid' => 'shopify_8123456789012_0',
'targets' => [
['account' => 'ebay:718', 'price' => 45.0],
['account' => 'vinted:22', 'price' => 40.0],
],
],
]);
$data = json_decode((string) $res->getBody(), true);{
"vid": "shopify_8123456789012_0",
"fluf_id": 326428506,
"results": [
{
"channel": "ebay",
"account": "ebay:718",
"operation": "create",
"outcome": "ok",
"listing_id": "123456789",
"message": "Listed at 45."
},
{
"channel": "vinted",
"account": "vinted:22",
"operation": "create",
"outcome": "queued",
"message": "Accepted. This channel publishes asynchronously — poll GET /items/{vid}."
}
]
}Body parameters
| Field | Type | Required | Notes |
|---|---|---|---|
vid | string | yes | Product id, e.g. shopify_8123456789012_0 or fluf_326428506_0 |
targets | array | yes | 1-20 target objects. Only named targets are touched |
targets[].account | string | usually | The id from GET /accounts, e.g. ebay:718 |
targets[].channel | string | alternative | Channel slug, used with connection_id when not sending account |
targets[].connection_id | integer | alternative | Marketplace connection id, used with channel |
targets[].price | number | no | Price for this account, in major units. Omit to leave the current override alone |
targets[].relist | boolean | no | Force a fresh listing in place of one already live. Default false |
Omitted targets are a no-op. If an item is live on eBay, Vinted and Etsy, and your request names only eBay, the Vinted and Etsy listings are left alone. Delisting is not implied by omission — use DELETE /items.
Price overrides are scoped to {product, channel, account}. They do not change the product's own price, and they persist so future relists on that account use the same override.
Relisting
relist: true only matters when the target is already live: a dead, sold, or removed listing is already treated as not-listed, so a plain POST /items without the flag republishes it. Use relist to reset a listing's position in a marketplace's search results without waiting for it to sell or expire.
It is always asynchronous — the response reports outcome: "queued", never "ok" — and not every channel supports it; an unsupported channel returns outcome: "failed", code: "relist_not_supported".
Returns
The HTTP status is the overall result; results is per target.
| Status | Meaning |
|---|---|
201 | At least one listing was created |
200 | Applied, but nothing new was created: updates, no-ops, skipped targets, or per-target failures |
202 | At least one target was queued |
Per target, branch on outcome, not on message text.
| Field | Values |
|---|---|
operation | create, reprice, relist, noop |
outcome | ok, queued, failed, skipped |
queued is accepted work, not a failed publish. Some channels complete asynchronously; poll GET /items/{vid} until the listing appears or reports an error.
Warnings
A successful target may include warnings when the marketplace accepted the listing but not exactly as submitted.
Known warning codes: brand_not_recognised, size_not_mapped, category_fallback, attribute_dropped, photos_reduced, description_truncated.
Surface warnings to the seller. They usually explain discoverability issues such as an unmapped size or a brand fallback.
{
"code": "brand_not_recognised",
"message": "Brand was listed as Unbranded.",
"context": { "requested": "Bape", "applied": "Unbranded" }
}Errors
Request-level errors use a stable code and human message:
| HTTP | Code | Meaning |
|---|---|---|
400 | missing_targets | targets is missing or empty |
400 | too_many_targets | More than 20 targets were sent |
400 | missing_product_ref | vid is missing |
400 | bad_vid | vid is malformed |
401 / 403 | varies | Token or plan refused. See Authentication |
404 / 422 | product_not_found | The product cannot be resolved on this account |
409 | publish_in_progress | Another request is already publishing this product |
409 is a wait-and-retry condition. It exists to stop two concurrent requests creating duplicate marketplace listings.
Target-level failures stay inside results[] with outcome: "failed" and a target code. Common codes include bad_target, unknown_channel, channel_unavailable, update_failed, listing_failed, crosslist_failed, already_listed, and no_result.
Something missing? Email [email protected] — we prioritise by what people actually ask for.
