Offer Feed Criteria¶
This page describes the requirements and validation rules that iUsed applies to Refurbisher Offer Feeds during import. It is intended for the iUsed team and Flooris developers involved in onboarding or troubleshooting Refurbisher integrations.
Partner-facing documentation
The full technical specification for Refurbisher partners is published at the iUsed Partner Documentation portal:
How the import works¶
The IRP polls each active Refurbisher's offer feed approximately every 5 minutes via ImportRefurbisherOffersJob. The
job:
- Fetches all offers from the Refurbisher's endpoint (HTTP or sFTP)
- Maps the raw feed fields to IRP fields using the Refurbisher's connector config
- Upserts each offer using
(refurbisher_id, sku, ean)as the identity key - Validates each offer via
RefurbisherOfferValidationService - Sets
stock_available = 0on any offer not present in the current feed
Pagination¶
Some Refurbisher offer feed endpoints return results across multiple pages instead of a single response. If pagination is not enabled in the Refurbisher's connector config, the IRP only imports the first page of the feed — all subsequent offers are silently missing from that import run.
The Refurbisher Connector config (config/refurbishers.json) exposes two parameters on the offers block to control
this:
"paginate": true,
"pagination_parameter": "pageNumber",
| Parameter | Type | Description |
|---|---|---|
paginate |
boolean |
Enables pagination. When true, the IRP keeps requesting pages until an empty/last page is returned. |
pagination_parameter |
string |
Name of the query/request parameter the Refurbisher's endpoint expects to receive the page number on, e.g. pageNumber, page. |
Check the partner's feed documentation before onboarding
Whether pagination is required — and which parameter name and page-numbering scheme (0-based vs. 1-based) the
endpoint expects — is defined by the Refurbisher's own offer feed documentation. Always verify this during
Discovery, since a missed paginate: true setting causes an incomplete feed import without producing any
validation error: the offers on later pages are simply never fetched.
Fixje
Fixje's Partner API v2 GET /api/v2/Products endpoint paginates its results. The connector config sets:
"paginate": true,
"pagination_parameter": "pageNumber",
Identity key¶
Each RefurbisherOffer record is uniquely identified by the combination:
refurbisher_id + sku + ean
EAN is part of the identity
If a Refurbisher submits a different EAN for an existing SKU, the IRP creates a new separate offer record — it does
not update the existing one. The old offer will have its stock set to 0. The new offer is immediately flagged as *
rejected* and requires manual review.
This is intentional: an EAN change may indicate a product substitution, re-packaging, or supplier error — all of which require human judgement before the product goes live.
Field requirements¶
| Field | DB column | Type | Max length | Required | Notes |
|---|---|---|---|---|---|
sku |
sku |
varchar(255) |
255 | Yes | Unique per Refurbisher |
ean |
ean |
varchar(25) |
25 | No | Part of identity key |
name |
name |
string |
— | Yes | Product name |
stock_available |
stock_available |
integer |
— | Yes | 0 = out of stock |
price_purchase_amount |
price_purchase_amount |
integer (cents) |
— | Yes | Stored after VAT/margin deduction |
price_consumer_amount |
price_consumer_amount |
integer (cents) |
— | Yes | Consumer-facing price |
tax_percentage |
tax_percentage |
decimal |
— | No | Fallback from connector config |
currency_iso |
currency_iso |
varchar(3) |
— | No | Defaults to EUR |
Validation rules¶
The RefurbisherOfferValidationService runs after every create or update. Validation is applied to the **raw feed data
** (before database write) for length checks, and to the persisted model for duplicate checks.
| Rule | Checked against | Trigger |
|---|---|---|
| SKU exceeds 255 characters | Raw feed value | Rejected |
| EAN exceeds 25 characters | Raw feed value | Rejected |
| Duplicate SKU with different EAN | Persisted DB records | Rejected |
Why raw feed data for length checks?
MySQL silently truncates strings that exceed the column length. Checking strlen() on the already-saved model would
always pass (the value is already truncated). Validation therefore runs against the unmapped feed value before any DB
write.
Rejected offers:
- Have
validation_status = 'rejected' - Have
validation_message(JSON array) with one or more human-readable reasons - Are visible on the Refurbisher Offer index page and filterable by validation status
- Are not pushed to Shopware until the issue is resolved or the offer is manually approved
Viewing validation results¶
In the IRP:
- Navigate to Refurbisher Offers — filter by Validation status: Rejected
- Open any rejected offer to see the specific rejection reason(s)
In the reporting cache:
Each import run produces a report at:
/reporting/refurbisher_offers/{refurbisher_id}/{import_uuid}
The report includes a rejected_offers count and detailed event log.
Common issues¶
Duplicate SKU with changed EAN¶
Symptom: An offer appears as rejected with the message "Duplicate SKU: another offer exists for this SKU with a
different EAN code."
Cause: The Refurbisher has changed the EAN associated with a SKU that already exists in the IRP.
Resolution:
- Confirm with the Refurbisher whether the EAN change is intentional.
- If intentional: soft-delete the old offer (if not linked to a ProductVariant) and approve the new offer.
- If the old offer is linked to a ProductVariant: re-link the ProductVariant to the new offer, then soft-delete the old offer.
- If the EAN change is an error: ask the Refurbisher to correct the feed and revert to the original EAN.
SKU or EAN too long¶
Symptom: An offer is rejected with "SKU exceeds maximum length" or "EAN exceeds maximum length".
Cause: The Refurbisher's feed contains a value that is longer than the database column allows.
Resolution: Contact the Refurbisher and request a shorter identifier. The IRP cannot truncate values automatically because that would corrupt the identity key.
Offers not disappearing after stock runs out¶
Cause: The Refurbisher is submitting a partial feed (only changed offers) instead of the full catalogue.
Resolution: The IRP requires a complete feed on every poll. Ask the Refurbisher to always include all products,
with stock_available: 0 for out-of-stock items.
Only a fraction of the Refurbisher's catalogue is imported¶
Symptom: No offers are rejected, but the number of imported offers is far lower than the Refurbisher's actual
catalogue size, and offers that do exist at the Refurbisher never appear in the IRP.
Cause: The Refurbisher's offer feed endpoint paginates its results, but paginate is not enabled (or
pagination_parameter is missing/incorrect) in the connector config — see Pagination. The IRP only
fetches the first page and treats it as the complete feed, so stock_available also gets incorrectly set to 0 for
every offer beyond page 1.
Resolution: Check the Refurbisher's feed documentation for pagination requirements and set paginate: true with
the correct pagination_parameter in config/refurbishers.json.
Database cleanup after EAN-truncation bug¶
Prior to the varchar(25) column expansion (migration 2026_07_04_192053), the ean column was varchar(15). EAN
codes longer than 15 characters were silently truncated, causing duplicate offer records to be created on every import
run.
SQL instructions for identifying and cleaning up these duplicates are in:
docs/refurbisher-offer-cleanup.md in the IRP repository.