Listing Endpoints

Create, list, read, edit, publish, and withdraw supplier listings.

Updated 18 August 2026

The listing API manages inventory owned by the supplier account attached to the API key. All paths below are relative to https://www.rebattery.io/api/v1.

Endpoint summary

MethodPathScopePurpose
POST/listingslistings:writeCreate a draft or create and publish a ready listing.
GET/listingslistings:readList owned listings using offset pagination.
GET/listings/{listingId}listings:read or listings:writeRead one complete owned listing and its revision.
PATCH/listings/{listingId}listings:writeApply a sparse edit without changing the public slug.
POST/listings/{listingId}/imageslistings:writeAppend images without deleting or reordering existing media.
POST/listings/{listingId}/publishlistings:writePublish a ready draft or withdrawn listing.
POST/listings/{listingId}/withdrawlistings:writeWithdraw a published listing without deleting it.

Every request needs Authorization: Bearer <your-api-key>. Every mutation needs a unique Idempotency-Key. Existing-resource mutations also need the current quoted revision in If-Match.

Create a listing

POST /listings accepts incomplete drafts and complete published listings. Draft creation is the safest way to validate a new integration.

Example request

Shell
curl --fail-with-body --silent --show-error \
  --request POST 'https://www.rebattery.io/api/v1/listings' \
  --header "Authorization: Bearer $REBATTERY_API_KEY" \
  --header 'Idempotency-Key: erp-inv-2026-001-create' \
  --header 'Content-Type: application/json' \
  --data '{
    "status": "draft",
    "reference": "INV-2026-001",
    "manufacturer": "Tesla",
    "model": "Model 3",
    "chemistry": "NMC",
    "format": "Pack",
    "quantity": 4,
    "condition": "excellent",
    "conditionDeclaration": "none_damaged",
    "packKwh": 55,
    "packWeightKg": 480,
    "yearManufacture": 2019,
    "originalApplication": "EV / automotive",
    "soh": 82,
    "channelMode": "sale",
    "minimumOfferPrice": 4000,
    "currency": "GBP",
    "useAccountAddress": true
  }'

Success response

HTTP
HTTP/2 201 Created
content-type: application/json
cache-control: private, no-store
JSON
{
  "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "reference": "INV-2026-001",
  "slug": "tesla-model-3-55kwh-nmc-a1b2",
  "status": "draft",
  "revision": 1
}

ReBattery generates the title and slug. Omitted battery facts remain absent. channelMode defaults to sale, but integrations should send it explicitly so the commercial intent is auditable.

When status is published, ReBattery persists the draft, ingests its images, runs canonical readiness, and publishes only if all required facts are present. A failed publication may return a retained draft ID but is never reported as published.

List owned listings

GET /listings returns newest listings first.

Query parameterTypeDefaultRules
limitinteger50From 1 through 100.
offsetinteger0Non-negative safe integer. An offset beyond the end returns an empty page.
Shell
curl --fail-with-body --silent --show-error \
  'https://www.rebattery.io/api/v1/listings?limit=50&offset=0' \
  --header "Authorization: Bearer $REBATTERY_API_KEY"
JSON
{
  "listings": [
    {
      "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
      "reference": "INV-2026-001",
      "slug": "tesla-model-3-55kwh-nmc-a1b2",
      "status": "published",
      "imageCount": 1,
      "revision": 12
    }
  ],
  "pagination": {
    "limit": 50,
    "offset": 0,
    "total": 1,
    "nextOffset": null
  }
}

When nextOffset is a number, use it as the next request's offset. This endpoint never returns another supplier account's inventory.

Read one listing

GET /listings/{listingId} returns the complete editable camelCase resource, ordered images, available quantity, and a numeric revision. The response ETag is that revision in quotes.

Shell
curl --include --silent --show-error \
  'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6' \
  --header "Authorization: Bearer $REBATTERY_API_KEY"
HTTP
HTTP/2 200 OK
etag: "12"
content-type: application/json
cache-control: private, no-store
JSON
{
  "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "reference": "INV-2026-001",
  "title": "Tesla Model 3 55kWh",
  "slug": "tesla-model-3-55kwh-nmc-a1b2",
  "status": "published",
  "channelMode": "sale",
  "manufacturer": "Tesla",
  "model": "Model 3",
  "quantity": 4,
  "quantityAvailable": 4,
  "condition": "functional",
  "soh": 82,
  "imageCount": 1,
  "images": [
    {
      "id": "1d877394-dad0-4f61-92fc-f728fc9f6b88",
      "url": "https://example.com/model-3-pack.jpg",
      "sortOrder": 0,
      "filename": "model-3-pack.jpg"
    }
  ],
  "revision": 12,
  "createdAt": "2026-08-14T10:00:00.000Z",
  "updatedAt": "2026-08-14T11:00:00.000Z"
}

Nullable editable fields are returned as null. Clients should ignore unknown fields so backward-compatible response additions do not break them.

Edit a listing

PATCH /listings/{listingId} is sparse. Omitted fields remain unchanged; explicit null clears only a supported nullable field.

First read the listing and retain its ETag. Then send that value in If-Match with a new idempotency key:

Shell
curl --fail-with-body --silent --show-error \
  --request PATCH 'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6' \
  --header "Authorization: Bearer $REBATTERY_API_KEY" \
  --header 'Idempotency-Key: erp-inv-2026-001-edit-9' \
  --header 'If-Match: "12"' \
  --header 'Content-Type: application/json' \
  --data '{
    "quantity": 8,
    "soh": 81,
    "description": "Retested 14 Aug 2026"
  }'
HTTP
HTTP/2 200 OK
etag: "13"
JSON
{
  "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "revision": 13
}

ReBattery rebuilds the canonical title from the merged facts and never changes the public slug through this endpoint. title, slug, status, images, available quantity, ownership, grouping, and internal JSON are not writable.

Quantity edits preserve committed units and return 409 if the new quantity is too small. A published listing must remain publish-ready. Grouped and completed listings cannot be edited here, and channel changes are rejected after stock is committed or commercial activity begins.

buyItNowPrice and minimumOfferPrice are mutually exclusive. To switch terms, clear one and set the other in the same request:

JSON
{
  "minimumOfferPrice": null,
  "buyItNowPrice": 4200
}

Append images

POST /listings/{listingId}/images appends images in request order. The operation is all-or-none, permits at most ten total listing images, and never deletes, replaces, or reorders existing media or supplier documents.

Shell
curl --fail-with-body --silent --show-error \
  --request POST 'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6/images' \
  --header "Authorization: Bearer $REBATTERY_API_KEY" \
  --header 'Idempotency-Key: erp-inv-2026-001-images-2' \
  --header 'If-Match: "13"' \
  --header 'Content-Type: application/json' \
  --data '{
    "images": [
      {
        "url": "https://supplier.example/inventory/model-3-side.jpg",
        "filename": "model-3-side.jpg"
      }
    ]
  }'
JSON
{
  "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "revision": 14,
  "imagesAdded": 1
}

Each image uses exactly one HTTPS url or base64 payload. Decoded JPEG, PNG, WebP, and AVIF images are accepted. Each image is limited to 10 MB, and all base64 images in one request are limited to 25 MB.

Publish a listing

POST /listings/{listingId}/publish accepts an empty body or {}. It runs canonical readiness and makes visibility public atomically. Publishing an already published listing is a successful no-op.

Shell
curl --fail-with-body --silent --show-error \
  --request POST 'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6/publish' \
  --header "Authorization: Bearer $REBATTERY_API_KEY" \
  --header 'Idempotency-Key: erp-inv-2026-001-publish-1' \
  --header 'If-Match: "14"'
JSON
{
  "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "revision": 15,
  "status": "published"
}

Withdraw a listing

POST /listings/{listingId}/withdraw withdraws a published listing without deleting it. Withdrawing an already withdrawn listing is a successful no-op; withdrawing a draft is rejected.

Shell
curl --fail-with-body --silent --show-error \
  --request POST 'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6/withdraw' \
  --header "Authorization: Bearer $REBATTERY_API_KEY" \
  --header 'Idempotency-Key: erp-inv-2026-001-withdraw-1' \
  --header 'If-Match: "15"'
JSON
{
  "id": "6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6",
  "revision": 16,
  "status": "withdrawn"
}

Request field reference

All create fields are optional for a draft. PATCH accepts the same fields except status and images.

FieldType and valuesNotes
statusdraft or publishedCreate only; defaults to draft. Use lifecycle endpoints after creation.
channelModesale or recyclingDefaults to sale. The retired both value is invalid.
buyItNowPricepositive numberSale only; mutually exclusive with minimumOfferPrice.
minimumOfferPricepositive numberSale only; mutually exclusive with buyItNowPrice.
currencyGBP, EUR, or USDExplicit request value wins for either channel. When omitted, uses the supplier account's supported default-address currency.
referencestringSupplier or ERP reference; not used in the public slug.
descriptionstringSupplier-provided listing description.
manufacturer, model, chemistrystringMaterial battery identity facts.
formatPack, Module, or CellImproves completeness but is not a publication blocker.
quantityintegerFrom 1 through 2147483647; API listings are full-order only.
conditiongrade string or unit arraySee Condition data.
conditionDeclarationnone_damaged or some_damagedListing-level declaration for a uniform batch.
damageDetailsstringRequired for damaged stock and for published functional/end-of-life stock.
damageHistoryarrayValues: fire_affected, water_damaged, impact, thermal_event.
sohnumberState of health from 0 through 100.
packKwhpositive numberMaximum 999999.99; required to publish either channel.
packWeightKgpositive number when suppliedMaximum 999999.99; required to publish recycling listings and optional for resale listings.
yearManufactureintegerFrom 2000 through 2035.
originalApplicationenum stringSee accepted values below.
usageDetailsstringAdditional usage history.
dimL, dimW, dimHstringSupplier measurement text.
architectureVoltagestringArchitecture label or value supplied by the integration.
voltageNominal, socVoltspositive numberMaximum 999999.99.
partNumber, vin, cycleCountstringSupplier technical identifiers and measurements.
cellFormat, cellConfiguration, batteryPlatform, numCellsstringOptional technical details.
internalResistance, temperature, testDate, testMethod, stateOfChargestringOptional test evidence.
hasSafetyDataSheet, hasComplianceSDS, hasComplianceUN383, hasForkliftbooleanCompliance and collection capabilities.
collectionScheduleobjectMonday through Friday schedule; see below.
recyclingPackagingDetailsstringRequired to publish recycling listings.
useAccountAddressbooleanMutually exclusive with address. Resolves the account default address.
addressobjectStructured collection address; mutually exclusive with useAccountAddress.
imagesarrayCreate only. Use the image endpoint after creation.

Prices are capped at 9999999999.99. Numeric JSON values must be finite and within their documented bounds; numeric prefixes such as "50oops" are rejected rather than truncated.

The retired fields allowPartialOrders, minimumOrderQuantity, category, cellChemistryDetail, testDataOption, collectionAddress, locationCountry, locationRegion, and locationCity return 422 Unsupported field.

Condition data

Use a scalar grade when every unit has the same condition:

JSON
{
  "quantity": 4,
  "condition": "great",
  "conditionDeclaration": "none_damaged",
  "soh": 86
}

Valid grades are new, excellent, great, functional, and end_of_life.

For a mixed batch, send exactly one record for each unit in quantity:

JSON
{
  "quantity": 2,
  "condition": [
    {
      "id": "unit-1",
      "condition": "great",
      "soh": 86,
      "conditionDeclaration": "none_damaged"
    },
    {
      "id": "unit-2",
      "condition": "functional",
      "soh": 72,
      "conditionDeclaration": "some_damaged",
      "damageDetails": "Casing dent on the lower edge"
    }
  ]
}

Unit IDs must be unique. Each unit accepts only id, condition, soh, conditionDeclaration, and damageDetails. damageDetails is required when that unit declares some_damaged. Internal wizard and routing fields are not part of this API.

Published functional and end_of_life stock requires condition details even when no physical damage is declared; published functional stock also requires SOH.

Application values

originalApplication accepts:

  • EV / automotive
  • Home energy storage
  • Commercial / grid storage
  • Forklift / material handling
  • Telecom / UPS
  • E-mobility
  • Marine / maritime
  • New / never used
  • Other

Marketplace category filtering is derived from this canonical application; callers do not submit a separate category.

Address and schedule

Choose one address source.

Use the account's default address:

JSON
{
  "useAccountAddress": true
}

Or send a structured address:

JSON
{
  "address": {
    "address": "Unit 5, Industrial Estate",
    "city": "Coventry",
    "region": "West Midlands",
    "country": "United Kingdom",
    "countryCode": "GB",
    "postalCode": "CV1 2WT"
  }
}

collectionSchedule contains mon through fri. Each day requires enabled, start, and end; an enabled day uses HH:MM with start earlier than end.

JSON
{
  "collectionSchedule": {
    "mon": { "enabled": true, "start": "08:00", "end": "16:00" },
    "tue": { "enabled": false, "start": "", "end": "" },
    "wed": { "enabled": false, "start": "", "end": "" },
    "thu": { "enabled": false, "start": "", "end": "" },
    "fri": { "enabled": false, "start": "", "end": "" }
  }
}

Safe retries

After a timeout, retry the identical method, path, body, If-Match, and Idempotency-Key. Do not generate a new key merely because the response was lost: that would create a second mutation intent.

If a create response contains image_error, the draft exists and the identical retry resumes image ingestion on that listing. For any successful compact acknowledgement, call the ID read endpoint to obtain the complete current resource.

See Errors and retries for all status codes and recovery actions.