Channel publishing and T1 enrichment

These endpoints publish (export) products to a connected sales channel — Shopify, MercadoLibre, Amazon, Walmart, T1/Sears/Sanborns, among others — and expose the status of that publication. They also cover a T1-specific flow: checking how your product catalog is enriched and linked against what exists in T1.

Use them when you need to:

  • Publish (or republish) one, several, or all of your products to a channel.
  • Know whether a bulk publication finished, is still in progress, or partially failed.
  • Validate which products are ready to be published before launching the operation.
  • Review pending feeds for a specific product toward a channel.
  • Audit your catalog's linkage with T1/Sears/Sanborns.

Synchronous or asynchronous publishing, depending on volume

POST /products/export responds synchronously (201) when the operation is small, or asynchronously (202 + jobId) when the volume justifies it. Never assume which one you'll get: check the HTTP status of the response, and if it's 202, poll GET /products/export/{jobId} until the job finishes. This is the same async pattern used by CSV import and export.


Publish products to a channel

Publishes products to a connected sales channel. Responds synchronously or asynchronously depending on the volume of the operation.

channelIdstringrequired

ID of the target channel.

modestring

Scope of the selection: single, selected, filtered, or all.

selectionModestring

Alternate/legacy parameter related to selection scope. Coexists with mode in the same body.

productIdsstring[]

SKUs or IDs to publish. Required when mode is selected.

filtersobject

Catalog filters to apply. Required when mode is filtered.

targetMarketplacesstring[]

Target sub-markets within the channel, when the channel supports them (for example, MercadoLibre sites).

options.enableCategorizationboolean

Automatically categorizes the products in the target channel. Default: true.

options.forceRecategorizationboolean

Forces a new categorization even if the product already has one assigned. Default: false.

options.includeInventoryboolean

Includes available inventory in the published payload. Default: true.

options.includeVariantsboolean

Includes variants in the published payload. Default: true.

options.forceUpdateboolean

Forces the update even if the channel detects no changes. Default: false.

options.locationIdstring

Location from which to calculate the inventory to publish.

{
  "channelId": "65f2a0b1c4d5e6f7a8b9c0aa",
  "mode": "selected",
  "productIds": ["CAM-ROJO-M", "PAN-AZUL-32"],
  "options": { "includeInventory": true, "includeVariants": true }
}
201Synchronous publication — the operation was fully resolved before responding.
{
  "results": [
    { "sku": "CAM-ROJO-M", "success": true },
    { "sku": "PAN-AZUL-32", "success": false, "error": "CHANNEL_INACTIVE" }
  ]
}
202Asynchronous publication — the operation was queued, check its status with the jobId.
{
  "jobId": "exp_65f3a1b2c4d5e6f7a8b9c0e1",
  "metadata": { "async": true }
}

Required permission: products:export

Result shape not verified field by field

The name of the top-level key (results in the synchronous response, jobId + metadata.async in the asynchronous one) is confirmed in the code. The exact fields within each element of results[] beyond sku/success/error were not verified line by line — treat them as indicative and confirm against the actual response from your channel.

Direct path for single-product Shopify

When mode is single and the target channel is Shopify, the orchestrator invokes the Shopify integration directly (bypassing the intermediate channels service) to reduce latency. The response contract you see as an API consumer doesn't change — it's still a synchronous 201 — this is an internal implementation detail.

curl -X POST https://api.fenicia.io/products/export \
  -H "Authorization: Bearer fkapi_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "65f2a0b1c4d5e6f7a8b9c0aa",
    "mode": "selected",
    "productIds": ["CAM-ROJO-M", "PAN-AZUL-32"],
    "options": { "includeInventory": true, "includeVariants": true }
  }'

Check the status of a publication

Checks the status of a previously queued asynchronous publication.

jobIdstringrequired

ID of the job returned by POST /products/export (202) or by GET /products/export.

200
{
  "jobId": "exp_65f3a1b2c4d5e6f7a8b9c0e1",
  "status": "processing"
}
404
{ "code": "not-found", "message": "Export job not found" }

Required permission: products:export

Status object shape not confirmed beyond jobId and status

This API's audit report confirms that this endpoint returns "the job data," but does not confirm line by line the rest of the keys (for example, whether a percent or a per-product breakdown exists). Don't invent additional fields for this endpoint — poll and confirm the exact shape against your own job before depending on any specific field beyond jobId/status.

Tip

This is the same polling pattern used by CSV jobs (see Bulk CSV import and export): queue with the POST endpoint, then poll GET .../{jobId} until the status stops being transient.


List recent publications

Returns the most recent publications to channels (last 20).

200
{
  "jobs": []
}

Required permission: products:export

Only the last 20, no pagination

This endpoint does not accept pagination parameters — it always returns at most the 20 most recent publications for the tenant. If you need the full detail of a specific one, use GET /products/export/{jobId}.


Cancel an in-progress publication

Cancels an asynchronous publication that is still in progress.

jobIdstringrequired

ID of the job to cancel (path).

reasonstring

Reason for cancellation, for auditing.

{ "reason": "Datos incorrectos en el lote, se relanzará corregido" }
200
{
  "success": true,
  "message": "Export job cancelled"
}
404
{ "code": "not-found", "message": "Export job not found" }
400
{ "code": "cancel-failed", "message": "Export job cannot be cancelled in its current state" }

Required permission: products:export


Validate products before publishing

Groups products by how ready they are to be published to a channel, without executing the publication.

channelIdstringrequired

ID of the target channel.

selectionModestringrequired

all, selected, or filtered.

skusstring[]

Required when selectionMode is selected.

filtersobject

Required when selectionMode is filtered.

pagenumber

Page number.

limitnumber

Items per page. Default: 50.

{
  "channelId": "65f2a0b1c4d5e6f7a8b9c0aa",
  "selectionMode": "filtered",
  "filters": { "status": "active", "category": "playeras" },
  "limit": 50
}
200
{
  "published": [],
  "ready": [],
  "needs_review": [],
  "rejected": []
}

Required permission: products:read

Doesn't require products:export, despite the path

Unlike the rest of the endpoints under /products/export*, this one only requires products:read. If your API key only has read permission on products, this endpoint WILL work for you even if the others on this page return 403.

Exact shape of each group not confirmed

The four grouping keys (published, ready, needs_review, rejected) were confirmed, but not the exact shape of each element within those arrays — treat them as lists of products/SKUs with their reason, and confirm against your own response.


Pending feeds for a product

Returns the pending publication feeds for a product toward a specific destination.

skustringrequired

Product SKU.

destinationstring

Feed destination. Default: walmart.

{ "sku": "CAM-ROJO-M", "destination": "walmart" }
200
{
  "feeds": [],
  "sku": "CAM-ROJO-M",
  "destination": "walmart"
}
400
{ "code": "bad-request", "message": "Missing required field: sku" }

Required permission: products:read


T1 / Sears / Sanborns enrichment

These three endpoints share the same purpose: letting you audit how your Fenicia catalog is linked against the actual T1 catalog (which also operates Sears and Sanborns). Fenicia invokes the T1 integration directly to obtain this data — the same legitimate orchestrator pattern as the Shopify bypass described above.

T1 catalog products enriched with their sync status against Fenicia.

channelIdstringrequired

ID of the T1/Sears/Sanborns channel (path).

pagenumber

Page number. Default: 0.

limitnumber

Items per page. Default: 20.

searchstring

Search term.

200Each product carries a syncStatus field indicating its linkage status.
{
  "products": []
}
404
{ "code": "channel-not-found", "message": "Channel not found" }

Required permission: products:read

Fenicia products that already have a binding with T1.

channelIdstringrequired

ID of the T1/Sears/Sanborns channel (path).

pagenumber

Page number. Default: 0.

limitnumber

Items per page. Default: 20.

searchstring

Search term.

200
{
  "products": []
}

Required permission: products:read

All Fenicia products eligible for T1, indicating whether they already have a binding (hasBinding).

channelIdstringrequired

ID of the T1/Sears/Sanborns channel (path).

pagenumber

Page number. Default: 0.

limitnumber

Items per page. Default: 20.

searchstring

Search term.

inStockboolean

Filters only products with stock on hand.

200Each product carries hasBinding indicating whether it's already linked to T1.
{
  "products": []
}

Required permission: products:read

List wrapper not confirmed line by line

{ "products": [...] } is shown as an indicative shape consistent with other paginated listings in this domain (see Query the catalog); the audit report did not confirm the exact name of this key for the three T1 endpoints — verify it against your own response before depending on it.

curl "https://api.fenicia.io/products/channel/65f2a0b1c4d5e6f7a8b9c0aa/t1?limit=50" \
  -H "Authorization: Bearer fkapi_tu_api_key"

Errors

CodeStatusDescription
EXPORT_IN_PROGRESS409A publication is already in progress toward this channel; wait for it to finish before launching another.
CHANNEL_NOT_FOUND404The specified channel doesn't exist or doesn't belong to your tenant.
CHANNEL_INACTIVE400The channel exists but is inactive/disconnected.
NO_PRODUCTS400The selection (selected/filtered) resolved no products to publish.
PRODUCT_EXPORT_FAILED400Publication of one or more products to the channel failed.
not-found404No publication job exists with that jobId.
cancel-failed400The job cannot be cancelled in its current state (already finished or already cancelled).
bad-request400A required field is missing or a selectionMode/mode value is invalid.
channel-not-found404(T1) The specified channel doesn't exist or isn't of type T1/Sears/Sanborns.
invalid-channel-type400(T1) The specified channel exists but isn't of type T1/Sears/Sanborns.
t1-integration-error / t1-api-error500(T1) Communication with the T1 integration or API failed.
auth:invalid_token401The API key is invalid or has been revoked.
auth:permission_denied403The API key doesn't have the required permission (products:read or products:export depending on the endpoint).

See the full error catalog for the rest of the possible codes.

Error code format not unified in this domain

Unlike the Orders domain (which always uses namespace:snake_case), Products mixes legacy conventions: you'll see SCREAMING_SNAKE_CASE codes (CHANNEL_NOT_FOUND), kebab-case (cancel-failed), and, on other pages in this domain, bad-request/sub-code. Branch your logic on the exact value of the code field, not on its format.

Next steps