# Recoverable stock and Signal advert publishing

Signal 0.1.0 · trusted-server reference example · Node 22.18 or later.

Download `signal-trade-stock-sync.mjs` and `signal-trade-stock-publishing-example.mjs` into one directory. No packages are needed. Use an approved dealership's `stock:manage` and `advert:manage` keys for the same business. Keep both in server environment variables `SIGNAL_STOCK_API_KEY` and `SIGNAL_ADVERT_API_KEY`; never put them in a browser, plan, journal or source control.

This example **publishes a real Signal advert**, then pauses it, reverses the two photos, removes the first uploaded photo and optionally republishes. It requires two photos of the actual vehicle and an explicit final status. Run it only for stock you are authorized to advertise. It does not publish to Auto Trader or BCA.

## Prepare the plan

Create a private `PLAN.json` with the exact vehicle details, changes, full advert fields, reviewed asking amount/VAT tuple and paths to two original JPG, PNG or WebP files. Each photo must be complete, correctly typed, no more than 4 MB and within the API's 40 megapixel limit. Never use an example vehicle or contact as a real advert.

```json
{
  "baseUrl": "https://signalautomotive.uk",
  "publicationConfirmed": true,
  "finalStatus": "paused",
  "vehicle": {
    "stockNumber": "YOUR-STOCK-NUMBER",
    "registration": "YOUR-REGISTRATION",
    "make": "ACTUAL MAKE",
    "model": "ACTUAL MODEL",
    "year": 2020,
    "mileage": 40000,
    "askingPricePence": 1000000,
    "askingPriceVatBasis": "vat_not_applicable",
    "askingPriceVatRateBasisPoints": null
  },
  "changes": { "mileage": 40000 },
  "advert": {
    "kind": "car",
    "bodyType": "hatchback",
    "sellerType": "dealer",
    "sellerName": "YOUR DEALERSHIP",
    "town": "YOUR TOWN",
    "phone": "YOUR VALID PHONE",
    "email": "YOUR VALID EMAIL",
    "description": "YOUR ACTUAL VEHICLE DESCRIPTION OF AT LEAST FORTY CHARACTERS",
    "transmission": "manual",
    "price": "10000.00",
    "vat": "no_vat",
    "priceVatBasis": "vat_not_applicable",
    "priceVatRateBasisPoints": null,
    "priceConfirmed": true,
    "expectedSourcePrice": {
      "amountPence": 1000000,
      "vatBasis": "vat_not_applicable",
      "vatRateBasisPoints": null
    }
  },
  "photos": [
    { "path": "C:/PRIVATE/ACTUAL-FRONT.png", "mediaType": "image/png" },
    { "path": "C:/PRIVATE/ACTUAL-REAR.png", "mediaType": "image/png" }
  ]
}
```

Replace every placeholder, review the correct VAT basis and rate and set `publicationConfirmed` only after confirming your right to sell and share the listed contact details with signed-in buyers. VAT scheme eligibility is not established by entering a value. The example does not infer a rate or substitute a market estimate for the recorded asking price.

Run from the trusted server:

```text
node signal-trade-stock-publishing-example.mjs PLAN.json JOURNAL.sqlite
```

## Resume an interrupted run

Run the same command with the same plan, journal and originating API keys. Each pending request was saved with its UUID and original version before it was sent. The example retries that saved request exactly. It retains original photo bytes in the journal so a retry never uploads changed bytes. Completed receipts are kept separately from freshly read current records. Two processes using the same journal select the same saved request for each step; the API confirms one original save.

An HTTP 409 requires reconciliation. Keep the journal and inspect the current record; do not replace the saved request's version, UUID, asking-price tuple or bytes to bypass the conflict. A new plan needs a new journal. A receipt from a removed photo does not restore it; an old publish receipt does not republish an advert that was subsequently paused. Revoked keys or lost trade access cannot read receipts or current records.

The journal contains your private vehicle plan, advert contacts, original photo bytes, confirmations and a local stock mirror. Keep it on protected local storage with access and backup controls. It contains no API key. Its change-feed checkpoint advances only after every event page and current record batch has been validated and saved. For an initial complete inventory mirror, use the existing stock mirror example; this publishing example demonstrates catch-up of retained change notices.

## Helper contracts

Node and Python stock helpers preserve legacy `createId` / `create_id` and `saleId` behavior. For confirmed stock creation use **operationId only** (Python: `create_id=None, operation_id=...`). Supplying both create IDs is rejected. Ordinary updates may add `operationId`; use it for each bulk row too. Sales continue to use their separate `saleId` contract.

Confirmed advert helpers require a saved operation UUID. `getStockAdvert` / `get_stock_advert` returns the current private owner projection. `createStockAdvert`, `changeStockAdvert`, `reorderStockAdvertPhotos`, `removeStockAdvertPhoto` and their Python counterparts return both the immutable `operation` and current `advert`. Upload helpers use `uploadId` and the existing immutable `upload` receipt. Save/publish/pause and photo changes require the exact current advert version when initially prepared. DELETE photo uses an optional JSON body; the confirmed helper sends `operationId` and `version`.

The `operation.result` records the original vehicle ID, recorded time and saved vehicle stamp, plus original advert version or photo IDs where applicable. It deliberately excludes staff names, contact details, request snapshots and credentials. Compare the receipt to the request being confirmed; the separately returned current record can have a later version or status.
