EPR EU Rep · Developers

API documentation

Everything you need to take a client from first question to a paid order in France, Germany and Spain, and to test it in the sandbox first.

Quickstart

The API is JSON over HTTPS at https://epreurep.com/api/v1. It needs no key, and it allows any origin, so you can call it from a browser. An integration has four steps:

StepCallWhat you get
1. Ask the questionsGET /france/questionnaireThe French questions, streams and plan rules
2. Price the answersPOST /quoteThe plan, the yearly total and the German licence estimate
3. Send the client to orderPOST /onboarding-linksA checkout link tied to your partner code
4. Test itPOST /sandbox/onboardingsA simulated run of each country's steps

Every order is completed by the client at the checkout link, because the client has to accept the terms, pay and sign the mandate. Use the demo partner code northstar while you build. Paid orders count for a partner only under that partner's own code.

Limit: 30 requests a minute per IP. Prices are in euro and exclude VAT. Scheme fees and eco-contributions are billed separately at cost. All partner and API orders are billed by EPRFranceRep SAS, whichever countries they cover.

How an order runs

After the client pays, each country runs these steps in this order. They are the same steps the sandbox plays back.

CountryStepsThe client does
Franceorder_paid, mandate_signed, registeredSigns the mandate
Germanyorder_paid, mandate_signed, dual_system_paid, appointed_in_lucid, registeredSigns the mandate, names us in LUCID
Spainorder_paid, mandate_signed, power_of_attorney_verified, registeredSigns the mandate

The client works in their own portal at portal.epreurep.com, which opens once they have paid. You follow their progress and your commission in the partner dashboard.

France

France needs three answers, the same three our checkout asks. All three are required. A request without one returns invalid_request and names the missing field.

FieldValuesQuestion
france.sells_toconsumers, mixed, businessesWho do you sell to in France?
france.streamsStream keys, see belowWhich product streams do you place on the French market?
france.tonnesup_to_1, 1_to_10, over_10How many tonnes of packaging a year?
curl https://epreurep.com/api/v1/france/questionnaire

The plan follows from the answers

You never choose a plan. /quote works it out, and the checkout re-prices it the same way. The rules run from the top; the first match wins.

AnswersPlanPrice a year
Over 10 tonnes, or more than 3 streamsEnterpriseWe quote it
sells_to is mixed or businesses, or professional packaging is listedPlus Pro€990, up to 3 streams
1 to 10 tonnes, or any Plus stream (WEEE, batteries)Plus€990, up to 3 streams
Any Enterprise streamEnterpriseQuoted by us first
Everything elseStandard€190 per stream, up to 3

Two streams are added for the client, and both count towards the 3. Household packaging is added unless they sell only to businesses. Professional packaging is added when they sell to businesses.

Streams

KeyLowest plan
household-packagingStandard
textiles-clothing-and-footwear-tlcStandard
electrical-and-electronic-equipmentPlus
batteriesPlus
furnitureStandard
toysStandard
sports-and-leisure-goodsStandard
diy-and-garden-toolsStandard
professional-packagingPlus Pro
building-products-pmcbEnterprise
tyresEnterprise
chemical-productsEnterprise
vehiclesEnterprise
lubricants-and-mineral-oilsEnterprise
medical-sharpsEnterprise
medicinesEnterprise
tobacco-productsEnterprise
fishing-gearEnterprise

Example

curl -X POST https://epreurep.com/api/v1/quote \
  -H 'content-type: application/json' \
  -d '{"france":{"sells_to":"consumers","streams":["toys"],"tonnes":"up_to_1"}}'
{
  "ok": true,
  "custom_quote": false,
  "total": 380,
  "currency": "EUR",
  "lines": [{ "country": "FR", "item": "France Standard", "eur": 380, "per": "year" }],
  "france": {
    "plan": "Standard",
    "streams": ["household-packaging", "toys"],
    "stream_count": 2,
    "tonnes": "up_to_1",
    "why": []
  },
  "note": "Indicative. Scheme fees and eco-contributions are billed separately at cost. Prices exclude VAT."
}

why lists what raised the plan, so you can show it to the client. It is empty on Standard.

Enterprise

Over 10 tonnes or more than 3 streams cannot be ordered through the API. /quote returns custom_quote: true and no price:

{
  "ok": true,
  "custom_quote": true,
  "total": null,
  "currency": "EUR",
  "lines": [],
  "france": {
    "plan": "Enterprise",
    "streams": ["household-packaging", "toys"],
    "stream_count": 2,
    "tonnes": "over_10",
    "why": ["Over 10 tonnes a year"]
  },
  "note": "Enterprise: more than 3 French streams or over 10 tonnes into France. We quote it first, so it cannot be ordered directly."
}

/onboarding-links still returns a url, with custom_quote: true. It opens a quote request and has no payment step. We send a written quote, and the client pays through a link from us. If the order also includes Germany or Spain, the whole order waits for the quote.

Germany

Germany's licence is priced on this year's packaging in kilos, by material, at Noventiz, our dual system. Kilos are optional in the order: the client enters them in their portal. The licence is passed on at cost, and we accept the LUCID appointment only once it is paid.

FieldTypeMeaning
germany.packaging_kgobjectKilos by material. Required unless already_licensed is true
germany.already_licensedbooleanAnother dual system already licenses all of it
germany.weeebooleanAdds German WEEE, €500 a year
germany.batteriesbooleanAdds German batteries, €500 a year
Material keyMaterial
paperPaper, board, carton
plasticPlastic
glassGlass
ferrousFerrous metals
aluAluminium
bevcartonBeverage cartons
compositeOther composites
otherOther materials
curl -X POST https://epreurep.com/api/v1/quote \
  -H 'content-type: application/json' \
  -d '{"germany":{"packaging_kg":{"paper":120,"plastic":40}}}'
{
  "ok": true,
  "custom_quote": false,
  "total": 149,
  "currency": "EUR",
  "lines": [{ "country": "DE", "item": "Germany packaging", "eur": 149, "per": "year" }],
  "note": "Indicative. Scheme fees and eco-contributions are billed separately at cost. Prices exclude VAT.",
  "germany": {
    "dual_system_estimate": { "eur": 121.43, "year": 2026, "vat_included": true, "charged": "in your client portal, before we accept the LUCID appointment" }
  }
}

dual_system_estimate is Noventiz's live price, VAT included, and it is separate from total. It is null if Noventiz gives no price at that moment; the quote itself still works.

When the licence is paid

Nothing is paid for the licence at checkout. Kilos you send with the order prefill the client's portal, where they can change them, see the live price and pay. They can sign the mandate and name us in LUCID first, but we accept the appointment only once the licence is paid.

With already_licensed: true, the client gives the licence details in the portal, and pays for any volume above what their other dual system covers.

Spain

FieldValuesMeaning
spain.planessential, standard€199 or €249 a year. Defaults to essential
spain.nifbooleanAdds the Spanish tax number, €249 one-off, for clients who have none

The NIF normally takes 5 to 6 days after the tax office appointment, and takes longer in busy periods. Foreign companies do not get a provisional NIF.

Sandbox

The sandbox plays back the steps from How an order runs. It creates no order, mandate, payment or registration, and it needs no partner code. The id holds the whole state, so nothing is stored.

curl -X POST https://epreurep.com/api/v1/sandbox/onboardings \
  -H 'content-type: application/json' \
  -d '{"countries":["DE"]}'

One step moves on every 5 seconds. Read the status with GET /sandbox/onboardings/{id}:

{
  "ok": true,
  "sandbox": true,
  "id": "sbx_...",
  "countries": [{
    "country": "DE",
    "status": "mandate_signed",
    "completed": ["order_paid", "mandate_signed"],
    "next": { "step": "dual_system_paid", "client_action_required": false },
    "registration_number": null
  }],
  "complete": false,
  "note": "Simulated. No order, mandate, payment or registration exists. Test numbers are not valid anywhere."
}

client_action_required is true for a step the client has to do personally, such as signing the mandate. When complete is true, each country has a test registration_number, for example DE-TEST-0000000000000. Test numbers are not valid anywhere.

The checks, quotes and links are live even while you test: /quote returns real prices and /onboarding-links returns a real checkout. Use northstar so that no test counts as a partner's lead. You can also run all of this from the sandbox on the developers page.

Partner codes

Apply on the partner page. Once approved, you get your own code, your dashboard, and 20% commission on what your clients pay us, renewals included. northstar is a demo code that earns nothing.

Errors

Errors return an HTTP status and a body with ok: false, an error code and a message you can show.

{ "ok": false, "error": "invalid_request", "message": "france.tonnes is required: up_to_1, 1_to_10 or over_10." }
StatuserrorMeaning
400invalid_jsonThe body isn't a JSON object
400invalid_requestA field is missing or has a value we don't accept. message names it
400unknown_partnerThe partner code doesn't exist
404not_foundUnknown path or sandbox id
405method_not_allowedUse POST on this path
429Over the limit of 30 requests a minute. Wait and try again

Endpoint reference

Base URL https://epreurep.com/api/v1. OpenAPI file

POST /api/v1/check

Which of your countries we cover, and a first guess at the product streams. The streams can go straight into /quote.

curl -X POST https://epreurep.com/api/v1/check \
  -H 'content-type: application/json' \
  -d '{"countries":["FR","DE"],"products":["cotton t-shirt"]}'

GET /api/v1/france/questionnaire

The questions our French checkout asks, with every stream, the lowest plan for each and the plan rules. Build your form from it.

curl https://epreurep.com/api/v1/france/questionnaire

POST /api/v1/quote

The yearly price, calculated by the same code as our checkout. France needs the three answers from the questionnaire and returns the plan they lead to. Germany needs this year's kilos and returns the licence estimate. custom_quote is true for Enterprise, which we quote first.

curl -X POST https://epreurep.com/api/v1/quote \
  -H 'content-type: application/json' \
  -d '{"france":{"sells_to":"consumers","streams":["toys"],"tonnes":"up_to_1"},"germany":{"packaging_kg":{"paper":120,"plastic":40}},"spain":{"plan":"essential","nif":true}}'

POST /api/v1/onboarding-links

A checkout link tied to your partner code, with the answers already in it and company and email filled in. Company and email stay in the URL fragment. Takes the same body as /quote. For Enterprise, the link opens the quote request and cannot be paid. northstar is a demo code for testing: use your own once you are a partner.

curl -X POST https://epreurep.com/api/v1/onboarding-links \
  -H 'content-type: application/json' \
  -d '{"partner":"northstar","germany":{"packaging_kg":{"paper":120,"plastic":40}},"company":"Example Ltd","email":"owner@example.com"}'

POST /api/v1/sandbox/onboardings

Starts a simulated onboarding for FR, DE or ES and returns an id. One step moves on every 5 seconds.

curl -X POST https://epreurep.com/api/v1/sandbox/onboardings \
  -H 'content-type: application/json' \
  -d '{"countries":["DE"]}'

GET /api/v1/sandbox/onboardings/{id}

The simulated status per country. client_action_required marks the steps a client has to do personally.

curl https://epreurep.com/api/v1/sandbox/onboardings/sbx_...

What no API can do

  • A client appoints the representative directly. The mandate names the real legal entity in each country.
  • Germany: the client registers in LUCID personally, and the mandate needs a handwritten or qualified electronic signature. The dual-system licence is paid in the portal, before we accept the appointment.
  • Spain: the appointment can need a power of attorney that the authorities verify.
  • Registrations and declarations are done by our team, so they run in days, not seconds.