Developers · Integration guide

How to add EPR to your software

You can add EPR for France, Germany and Spain to a SaaS or VAT platform in five steps, with no key. Your product asks the questions, and we do the regulated work behind it.

By Razvan Toma · Founder · Last checked 1 November 2026

Quick answer

Your software checks who needs EPR, shows a price and sends the client to a checkout link tied to your partner code. The client signs and pays, and our representative in each country does the registration and filing.

A VAT number or a VAT registration doesn't cover EPR. They are separate duties, with separate registers and deadlines, and a VAT registration doesn't make a company established for EPR.

Your product doesWe doThe client does
Spots who needs EPRActs as the legal representativeSigns the mandate
Asks the questionsRegisters and joins the schemesPays, and takes the personal steps
Shows the price and sends the linkFiles the declarationsProvides the figures

Step 1: find the clients who need it

You probably already hold what EPR starts from: the legal entity, the sales countries and the marketplaces. A seller shipping packaged goods to consumers in France, Germany or Spain has packaging EPR there, and needs a representative if it isn't established in that country.

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

/check confirms the countries we cover and gives a first guess at the French streams. Show it as a prompt to the client, not as a legal answer. See does a warehouse make a seller established?.

Step 2: ask the country questions

CountryWhat you needWhere it comes from
FranceWho they sell to, the streams, tonnes of packaging a yearGET /france/questionnaire
GermanyThis year's kilos per material, or already_licensedgermany.packaging_kg
SpainThe plan, and whether they need a Spanish tax numberspain.plan, spain.nif

Build your form from the questionnaire so it stays in step with our checkout.

Step 3: show the price

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}}'

/quote uses the same code as our checkout, so the price the client sees is the price they pay. Scheme fees and eco-contributions are billed separately at cost. For Germany, the response also estimates the dual-system licence, which the client pays in their portal before we accept the LUCID appointment.

Step 5: test it in the sandbox

/sandbox/onboardings runs each country's real steps with test numbers, so you can build your status screens before a real client goes through them. One step moves on every 5 seconds.

Node 18 or later

Save it as epr.mjs, then run node epr.mjs.

const API = 'https://epreurep.com/api/v1';
const post = async (path, body) => {
  const r = await fetch(API + path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
  const j = await r.json();
  if (!j.ok) throw new Error(j.message);
  return j;
};
const sleep = (ms) => new Promise((done) => setTimeout(done, ms));

// Client's answers, from your own form
const answers = { france: { sells_to: 'consumers', streams: ['toys'], tonnes: 'up_to_1' } };

const quote = await post('/quote', answers);
if (quote.custom_quote) console.log('Enterprise: ask us for a quote');
else console.log('Yearly total', quote.total, quote.currency);

// Test the steps in the sandbox before you go live
let run = await post('/sandbox/onboardings', { countries: ['FR'] });
while (!run.complete) {
  await sleep(5000);
  run = await (await fetch(`${API}/sandbox/onboardings/${run.id}`)).json();
}
console.log(run.countries[0].status, run.countries[0].completed);

// Use your own partner code
const link = await post('/onboarding-links', { partner: 'northstar', ...answers, company: 'Example Ltd', email: 'owner@example.com' });
console.log(link.url);

Python 3

import json, time, urllib.request

API = 'https://epreurep.com/api/v1'

def call(path, body=None):
    req = urllib.request.Request(API + path, json.dumps(body).encode() if body else None, {'content-type': 'application/json', 'user-agent': 'my-platform'})
    with urllib.request.urlopen(req) as r:
        return json.load(r)

# Client's answers, from your own form
answers = {'france': {'sells_to': 'consumers', 'streams': ['toys'], 'tonnes': 'up_to_1'}}

quote = call('/quote', answers)
print('Enterprise: ask us' if quote['custom_quote'] else (quote['total'], quote['currency']))

# Test the steps in the sandbox before you go live
run = call('/sandbox/onboardings', {'countries': ['FR']})
while not run['complete']:
    time.sleep(5)
    run = call('/sandbox/onboardings/' + run['id'])
print(run['countries'][0]['status'], run['countries'][0]['completed'])

# Use your own partner code
link = call('/onboarding-links', {'partner': 'northstar', **answers, 'company': 'Example Ltd', 'email': 'owner@example.com'})
print(link['url'])

After the link

Registration is a status that moves over days, not a call that returns a number. Design your screens for pauses, tell the client which step is theirs, and never show a registration before the register does.

CountryStepsWhat holds it up
FranceOrder, mandate, registeredEco-organisme membership issues the IDU
GermanyOrder, mandate, licence, LUCID, registeredThe client registers in LUCID personally
SpainOrder, mandate, power of attorney, registeredA Spanish tax number and verifying the power

You follow real clients, their fees and yours in the partner dashboard.

What can go wrong

What happensWhat to do
custom_quote is trueShow that we quote it, and send the link. It opens the quote request
invalid_requestShow the message, which names the field
HTTP 429You went over 30 requests a minute. Wait and retry
The client doesn't finishKeep the link and remind them. Nothing is ordered until they pay
The client already has a dual-system licenceSend germany.already_licensed instead of kilos

Referral, API or white label?

Start with a referral page and add the API when volume justifies it. If you want to own more of the journey, that's a white-label agreement. For the comparison, see EPR API vs white label vs referral. AI agents have their own quickstart.