Fabric documentation

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.csv

The 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.

  • 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.