Fulfillment Manager API · history

Record an order from before Manifold as history

PUT /historical-orders/{orderId}

Record an order from before Manifold as history, whole as the source reports it: number, date, lines with their tax lines, charges, money and outcome.

An open order is mirrored — re-recorded as the source works it, adopted as a live order at go live. History holds no stock, ships nothing, is never billed or posted to the ledger. orderId is derived from the order's history key, so a re-import names the same order; an id a live order holds is 409 OrderNotHistorical; a channel source is the providing app's alone (403 ChannelSourceForbidden). placedAt before 2000-01-01T00:00:00Z is refused (400). A currency other than the organization's home currency (GET /settings) is converted into it at a central bank's published rate for the last business day on or before placedAt — the bank local to the home currency first, then the ECB, then a cross through another bank's currency — and the order is kept in the home currency with its conversion (source, rate, date, the original money); a currency no bank covers is 409 HistoricalOrderCurrencyUnsupported, and the home currency unreadable is 502 InventoryUnavailable. Appends fulfillment.historical-order-recorded. Idempotent by content: the same body appends nothing, a different one replaces it, keeping Manifold's refunds and an adoption.

Access

Requires orders:write.

How scopes come from roles: Scopes and roles. Getting a token: Authentication and app tokens.

Retries

Idempotent by content: the same body appends nothing, a different one replaces it, keeping Manifold's refunds and an adoption.

Why every write carries a key: Idempotency and retries.

Facts appended

  • fulfillment.historical-order-recorded

What a fact is: Facts and the journal.

AI agent tool

No agent tool. history arrives in bulk, through an import's fulfillment.historical-order target or a channel app's own import; an agent brings orders in with the import tools, never one at a time

Connecting an agent: AI agents.

Example

curl
curl -X PUT 'https://fulfillment.getmanifold.io/historical-orders/ord_01a0e100-7250-7ee1-8a68-60718293a4b5' \
  -H "Authorization: Bearer $MANIFOLD_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
  "source": {
    "kind": "api"
  },
  "externalNumber": "#1188",
  "placedAt": "2025-11-14T03:22:10.000Z",
  "customer": {
    "personId": "psn_01a1588a-0c41-7d2e-9a10-3b5c6d7e8f90"
  },
  "currency": "AUD",
  "lines": [
    {
      "sku": "MUG-SPECKLE",
      "name": "Speckled stoneware mug",
      "quantity": 4,
      "unitPriceCents": 2400,
      "taxCents": 873,
      "externalId": "13011223001"
    },
    {
      "name": "Gift wrap",
      "quantity": 1,
      "unitPriceCents": 500,
      "taxCents": 45
    }
  ],
  "charges": {
    "shippingCents": 1000
  },
  "taxesIncluded": true,
  "money": {
    "totalCents": 11100,
    "paidCents": 11100,
    "refundedCents": 0
  },
  "outcome": "fulfilled"
}'
JavaScript (fetch)
const response = await fetch("https://fulfillment.getmanifold.io/historical-orders/ord_01a0e100-7250-7ee1-8a68-60718293a4b5", {
  method: "PUT",
  headers: { Authorization: `Bearer ${process.env.MANIFOLD_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "source": {
      "kind": "api"
    },
    "externalNumber": "#1188",
    "placedAt": "2025-11-14T03:22:10.000Z",
    "customer": {
      "personId": "psn_01a1588a-0c41-7d2e-9a10-3b5c6d7e8f90"
    },
    "currency": "AUD",
    "lines": [
      {
        "sku": "MUG-SPECKLE",
        "name": "Speckled stoneware mug",
        "quantity": 4,
        "unitPriceCents": 2400,
        "taxCents": 873,
        "externalId": "13011223001"
      },
      {
        "name": "Gift wrap",
        "quantity": 1,
        "unitPriceCents": 500,
        "taxCents": 45
      }
    ],
    "charges": {
      "shippingCents": 1000
    },
    "taxesIncluded": true,
    "money": {
      "totalCents": 11100,
      "paidCents": 11100,
      "refundedCents": 0
    },
    "outcome": "fulfilled"
  }),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const answer = await response.json();
Request body · application/json
{
  "source": {
    "kind": "api"
  },
  "externalNumber": "#1188",
  "placedAt": "2025-11-14T03:22:10.000Z",
  "customer": {
    "personId": "psn_01a1588a-0c41-7d2e-9a10-3b5c6d7e8f90"
  },
  "currency": "AUD",
  "lines": [
    {
      "sku": "MUG-SPECKLE",
      "name": "Speckled stoneware mug",
      "quantity": 4,
      "unitPriceCents": 2400,
      "taxCents": 873,
      "externalId": "13011223001"
    },
    {
      "name": "Gift wrap",
      "quantity": 1,
      "unitPriceCents": 500,
      "taxCents": 45
    }
  ],
  "charges": {
    "shippingCents": 1000
  },
  "taxesIncluded": true,
  "money": {
    "totalCents": 11100,
    "paidCents": 11100,
    "refundedCents": 0
  },
  "outcome": "fulfilled"
}
Response · 200 · application/json
{
  "id": "ord_01a0e100-7250-7ee1-8a68-60718293a4b5",
  "source": {
    "kind": "api"
  },
  "externalNumber": "#1188",
  "placedAt": "2025-11-14T03:22:10.000Z",
  "customer": {
    "personId": "psn_01a1588a-0c41-7d2e-9a10-3b5c6d7e8f90"
  },
  "currency": "AUD",
  "lines": [
    {
      "id": "lin_ad6f099f-b8df-e1c1-4d01-3ec78fc98587",
      "sku": "MUG-SPECKLE",
      "name": "Speckled stoneware mug",
      "quantity": 4,
      "unitPriceCents": 2400,
      "discountCents": 0,
      "taxCents": 873,
      "externalId": "13011223001"
    },
    {
      "id": "lin_1cb63d29-651e-6b92-7b22-38433247989f",
      "name": "Gift wrap",
      "quantity": 1,
      "unitPriceCents": 500,
      "discountCents": 0,
      "taxCents": 45
    }
  ],
  "charges": {
    "shippingCents": 1000,
    "shippingDiscountCents": 0,
    "discountCents": 0
  },
  "taxesIncluded": true,
  "money": {
    "totalCents": 11100,
    "paidCents": 11100,
    "refundedCents": 0
  },
  "outcome": "fulfilled",
  "fx": {
    "currency": "AUD",
    "home": "AUD",
    "rate": 1,
    "rateDate": "2025-11-14",
    "source": "same"
  },
  "refunds": [],
  "recordedAt": "2026-10-16T02:00:41.229Z",
  "updatedAt": "2026-10-16T02:00:41.229Z"
}

The panel writes this call in other languages. It does not send it: Manifold's APIs do not take calls from a web page on another site, so run the call from your own code or terminal. The whole API as OpenAPI: fulfillment-manager.openapi.json. Base URL https://fulfillment.getmanifold.io.

Errors

Every refusal is a status and a JSON body whose _tag names it. A refused call changes nothing.

401

RefusalWhat it meansWhat to do
Unauthorized reason The token is missing, expired or not one Manifold issued.Get a fresh token and send the call again.
TokenAlreadyUsed tokenId A single-use app token was already spent.Mint a new token for this call.

402

RefusalWhat it meansWhat to do
SubscriptionRequired status The organization's subscription has lapsed, so writes are refused.An owner settles billing. Reads still work meanwhile.

403

RefusalWhat it meansWhat to do
ChannelSourceForbidden channelId, reason Only the app that provides a channel may send orders from it.Send the order with source api, or from the channel's own app.
TenantRequired userId The token names no organization.Choose the organization, then get a new token.
ScopeRequired required The token's roles do not grant the scope this call needs.Ask an owner or admin for a role that grants it.

409

RefusalWhat it meansWhat to do
OrderNotHistorical orderId The order is a live order, not history.Use the live order's own calls.
HistoricalOrderCurrencyUnsupported orderId, currency, homeCurrency, reason The order's currency cannot be converted to the home currency. reason says why.Record it in a supported currency.
MirroredFromSource source This area is fed by your source system until the organization goes live.Make the change in the source system, or go live first.

502

RefusalWhat it meansWhat to do
InventoryUnavailable reason The inventory service did not answer, so nothing changed.Send the same request again shortly.

503

RefusalWhat it meansWhat to do
RequestLogUnavailable tokenId, reason The service could not record the request, so it did nothing.Send the same request again shortly.

How refusals work everywhere: Errors.