Publishing over the API
Create a destination connection, test it, start a run, and read what it wrote.
A publishing run sends your enriched catalog to one or more destinations and records what happened to every product. This page takes one destination from setup to a finished run.
You need an API key carrying publishing:read and publishing:write.
Approving a publish to a feed or a storefront also needs the owner role. A member key can do
every step here except approving the publish in step 5.
1. Pick a destination
The list of destinations is the same for every organization. Reading it needs a valid key and no particular scope.
curl https://fabric.inc/api/v1/publishing/destinations \
-H "Authorization: Bearer fab_sk_8Kq2..."{
"destinations": [
{
"defaultDelivery": "download",
"destinationKey": "fabric_canonical",
"fieldCount": 15,
"grain": "product",
"kind": "file",
"needsApproval": false,
"needsPrice": false,
"serialization": "csv",
"supportsDelta": false
}
]
}Check needsApproval first. It is false only for file destinations, which return a file
instead of publishing. Every other destination needs an owner's approval.
Destinations describes each one.
2. Create a connection
A connection links one destination to one brand, with the credential Fabric uses to deliver to it.
curl -X POST https://fabric.inc/api/v1/publishing/connections \
-H "Authorization: Bearer fab_sk_8Kq2..." \
-H "Content-Type: application/json" \
-d '{
"brandId": "brd_4b81de05",
"destinationKey": "acp_stripe",
"name": "Stripe — Northwind Outdoors",
"credential": "sk_live_..."
}'The credential is encrypted before it is stored and is never returned. Afterwards you can read only its last four characters: enough to tell two keys apart, not enough to use one.
{
"id": "cn_7f3a9c21",
"destinationKey": "acp_stripe",
"name": "Stripe — Northwind Outdoors",
"credentialHint": "9fQ2",
"hasCredential": true,
"delivery": { "scheme": "stripe" },
"enabled": false,
"lastTestOk": null,
"lastTestedAt": null,
"lastTestDetail": null,
"createdAt": "2026-09-05T14:22:07.183Z"
}Note
enabled: false is not an error. A new connection stays disabled until a test succeeds, so you
cannot publish by accident to a destination Fabric has never reached.
3. Test it
A test sends a real request to the destination and checks the reply. A passing test enables the connection.
curl -X POST https://fabric.inc/api/v1/publishing/connections/cn_7f3a9c21/test \
-H "Authorization: Bearer fab_sk_8Kq2..."{ "ok": true, "detail": "Stripe accepted this key for catalog imports." }A failed test returns 200 with "ok": false and a detail describing the problem. The
connection stays disabled, and a run that needs it delivers nothing. A failure here costs much less
than a failure halfway through a publish.
4. Dry-run it
A dry run builds every record and reports the result without delivering anything. It needs no approval, because you use it to check the changes before you approve them.
curl -X POST https://fabric.inc/api/v1/publishing/runs \
-H "Authorization: Bearer fab_sk_8Kq2..." \
-H "Content-Type: application/json" \
-d '{
"brandId": "brd_4b81de05",
"destinationKeys": ["acp_stripe"],
"mode": "dry_run"
}'Limit the run with viewId, listId or scope.skus. Without any of them it covers the brand's
whole catalog. mode is full, delta or dry_run. delta sends only what changed, and works
only on destinations whose supportsDelta is true.
5. Publish it
The same call with approve: true publishes for real. One call approves and publishes, and only an
owner key can approve a publish to a feed or storefront. A file destination ignores the
flag, because a download needs no approval.
curl -X POST https://fabric.inc/api/v1/publishing/runs \
-H "Authorization: Bearer fab_sk_8Kq2..." \
-H "Content-Type: application/json" \
-d '{
"brandId": "brd_4b81de05",
"destinationKeys": ["acp_stripe"],
"mode": "full",
"approve": true
}'An owner key publishes without a second step
There is no separate confirmation. An owner-scoped key holding publishing:write can approve its
own publish to a live destination in one request. Give integrations that only prepare or read a
member key instead.
{
"runId": "run_2c6f81a4",
"started": true,
"workflowId": "publish-run_2c6f81a4"
}Starting a run always returns 202, whether or not the workflow started. The run exists either
way, and started says which happened. A 500 here would invite a retry that publishes twice. When
started is false, the body includes an error, and you can read the run at the returned id.
6. Read the run
Read the run for the outcome, or register a webhook endpoint for
publish_run_completed and publish_run_failed and skip the polling.
curl https://fabric.inc/api/v1/publishing/runs/run_2c6f81a4 \
-H "Authorization: Bearer fab_sk_8Kq2..."{
"id": "run_2c6f81a4",
"brandId": "brd_4b81de05",
"mode": "full",
"status": "completed",
"productCount": 18412,
"approvedAt": "2026-09-05T14:22:07.183Z",
"approvedById": null,
"createdAt": "2026-09-05T14:22:07.183Z",
"startedAt": "2026-09-05T14:22:08.512Z",
"finishedAt": "2026-09-05T14:29:41.760Z",
"error": null,
"destinations": [
{
"destinationKey": "acp_stripe",
"status": "completed",
"recordCount": 18412,
"writtenCount": 18397,
"skippedCount": 15,
"failedCount": 0,
"artifactUri": "https://api.stripe.com/v2/commerce/product_catalog/imports/pci_9fQ2x4",
"reportKey": "org/org_2f9a1c/brand/brd_4b81de05/publishing/runs/run_2c6f81a4/acp_stripe.jsonl",
"error": null
}
]
}A run ends in one of completed, partial, failed or cancelled; pending and running mean
it is still going. partial means some destinations finished and others did not, so read
destinations rather than the top-level status alone.
approvedById is null when an API key approved the run, because the field identifies a person, not
a key. The run still records which key approved it, so the audit trail names that key.
A product missing data the destination requires is skipped and listed in the report at
reportKey. The run does not fail, so check skippedCount to see how many products were held
back.
Only an owner can cancel a running publish, at POST /v1/publishing/runs/{id}/cancel. Stopping a
storefront write halfway leaves the store partly updated, so cancelling needs the same role as
approving.
Downloading instead of publishing
For a file destination you can skip the steps above and download the file in one request.
curl -X POST https://fabric.inc/api/v1/publishing/export \
-H "Authorization: Bearer fab_sk_8Kq2..." \
-H "Content-Type: application/json" \
-d '{ "brandId": "brd_4b81de05", "destinationKey": "fabric_canonical" }' \
-o catalog.csvThe X-Fabric-Records and X-Fabric-Skipped response headers give the counts, so you do not need
to parse the body. Above your synchronous row cap the request returns
too_large; start a run instead. This endpoint refuses a feed or
storefront destination at any size, because it cannot record the approval and audit trail a
publish needs.
Related
- Destinations: every destination, its kind, and the download cap.
- Authentication: scopes, roles and what a key can do.
- Errors: every error code the API returns.