Skip to content

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:

https://partner-docs.refurbishedcompany.com


How the import works

The IRP polls each active Refurbisher's offer feed approximately every 5 minutes via ImportRefurbisherOffersJob. The job:

  1. Fetches all offers from the Refurbisher's endpoint (HTTP or sFTP)
  2. Maps the raw feed fields to IRP fields using the Refurbisher's connector config
  3. Upserts each offer using (refurbisher_id, sku, ean) as the identity key
  4. Validates each offer via RefurbisherOfferValidationService
  5. Sets stock_available = 0 on 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:

  1. Confirm with the Refurbisher whether the EAN change is intentional.
  2. If intentional: soft-delete the old offer (if not linked to a ProductVariant) and approve the new offer.
  3. If the old offer is linked to a ProductVariant: re-link the ProductVariant to the new offer, then soft-delete the old offer.
  4. 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.