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
| Method | Path | Scope | Purpose |
|---|---|---|---|
POST | /listings | listings:write | Create a draft or create and publish a ready listing. |
GET | /listings | listings:read | List owned listings using offset pagination. |
GET | /listings/{listingId} | listings:read or listings:write | Read one complete owned listing and its revision. |
PATCH | /listings/{listingId} | listings:write | Apply a sparse edit without changing the public slug. |
POST | /listings/{listingId}/images | listings:write | Append images without deleting or reordering existing media. |
POST | /listings/{listingId}/publish | listings:write | Publish a ready draft or withdrawn listing. |
POST | /listings/{listingId}/withdraw | listings:write | Withdraw 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
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/2 201 Created
content-type: application/json
cache-control: private, no-store
{
"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 parameter | Type | Default | Rules |
|---|---|---|---|
limit | integer | 50 | From 1 through 100. |
offset | integer | 0 | Non-negative safe integer. An offset beyond the end returns an empty page. |
curl --fail-with-body --silent --show-error \
'https://www.rebattery.io/api/v1/listings?limit=50&offset=0' \
--header "Authorization: Bearer $REBATTERY_API_KEY"
{
"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.
curl --include --silent --show-error \
'https://www.rebattery.io/api/v1/listings/6a27eb6e-e5f6-4457-a55d-0ad4c4fe1cd6' \
--header "Authorization: Bearer $REBATTERY_API_KEY"
HTTP/2 200 OK
etag: "12"
content-type: application/json
cache-control: private, no-store
{
"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:
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/2 200 OK
etag: "13"
{
"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:
{
"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.
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"
}
]
}'
{
"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.
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"'
{
"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.
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"'
{
"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.
| Field | Type and values | Notes |
|---|---|---|
status | draft or published | Create only; defaults to draft. Use lifecycle endpoints after creation. |
channelMode | sale or recycling | Defaults to sale. The retired both value is invalid. |
buyItNowPrice | positive number | Sale only; mutually exclusive with minimumOfferPrice. |
minimumOfferPrice | positive number | Sale only; mutually exclusive with buyItNowPrice. |
currency | GBP, EUR, or USD | Explicit request value wins for either channel. When omitted, uses the supplier account's supported default-address currency. |
reference | string | Supplier or ERP reference; not used in the public slug. |
description | string | Supplier-provided listing description. |
manufacturer, model, chemistry | string | Material battery identity facts. |
format | Pack, Module, or Cell | Improves completeness but is not a publication blocker. |
quantity | integer | From 1 through 2147483647; API listings are full-order only. |
condition | grade string or unit array | See Condition data. |
conditionDeclaration | none_damaged or some_damaged | Listing-level declaration for a uniform batch. |
damageDetails | string | Required for damaged stock and for published functional/end-of-life stock. |
damageHistory | array | Values: fire_affected, water_damaged, impact, thermal_event. |
soh | number | State of health from 0 through 100. |
packKwh | positive number | Maximum 999999.99; required to publish either channel. |
packWeightKg | positive number when supplied | Maximum 999999.99; required to publish recycling listings and optional for resale listings. |
yearManufacture | integer | From 2000 through 2035. |
originalApplication | enum string | See accepted values below. |
usageDetails | string | Additional usage history. |
dimL, dimW, dimH | string | Supplier measurement text. |
architectureVoltage | string | Architecture label or value supplied by the integration. |
voltageNominal, socVolts | positive number | Maximum 999999.99. |
partNumber, vin, cycleCount | string | Supplier technical identifiers and measurements. |
cellFormat, cellConfiguration, batteryPlatform, numCells | string | Optional technical details. |
internalResistance, temperature, testDate, testMethod, stateOfCharge | string | Optional test evidence. |
hasSafetyDataSheet, hasComplianceSDS, hasComplianceUN383, hasForklift | boolean | Compliance and collection capabilities. |
collectionSchedule | object | Monday through Friday schedule; see below. |
recyclingPackagingDetails | string | Required to publish recycling listings. |
useAccountAddress | boolean | Mutually exclusive with address. Resolves the account default address. |
address | object | Structured collection address; mutually exclusive with useAccountAddress. |
images | array | Create 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:
{
"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:
{
"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 / automotiveHome energy storageCommercial / grid storageForklift / material handlingTelecom / UPSE-mobilityMarine / maritimeNew / never usedOther
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:
{
"useAccountAddress": true
}
Or send a structured address:
{
"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.
{
"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.