# EPR API for AI agents

Any agent that can make HTTP requests can check obligations, quote and run a full simulated onboarding here, with no key and no account. A person still signs and pays.

## Quick answer

Yes, an AI agent can test this API on its own. It needs HTTP GET and POST and nothing else. A browsing-only agent can read these pages, but it can't run the steps.

| What the agent does | Endpoint | Effect |
|---|---|---|
| Check countries and likely streams | `POST /check` | Returns data only |
| Read the France questions | `GET /france/questionnaire` | Returns data only |
| Quote from live prices | `POST /quote` | Returns data only |
| Simulate each country's steps | `POST /sandbox/onboardings` | Simulated, creates nothing |
| Prepare a checkout link for a person | `POST /onboarding-links` | Returns a link, nothing is ordered until a person pays |

The base URL is `https://epreurep.com/api/v1`. It takes JSON over HTTPS, with a limit of 30 requests a minute per IP. Prices exclude VAT.

## Files to read

| File | Use it for |
|---|---|
| [openapi.json](/developers/openapi.json) | The exact contract of every endpoint |
| [This page as Markdown](/developers/ai-agents/index.md) | This quickstart as plain text |
| [Full documentation as Markdown](/developers/docs/index.md) | Every rule, field and error |
| [llms.txt](/llms.txt) | All pages and the facts to quote about EPR EU Rep |

## Run the full test

These five steps are the whole journey. Each example shows what the live API returned.

### 1. Check the countries and streams

```bash
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"]}'
```

```json
{
  "ok": true,
  "countries": [
    { "code": "FR", "covered": true, "name": "France" },
    { "code": "DE", "covered": true, "name": "Germany" },
    { "code": "ES", "covered": true, "name": "Spain" }
  ],
  "streams": ["household-packaging", "textiles-clothing-and-footwear-tlc", "toys"],
  "indicative": true,
  "note": "A first guess from product words. Whether you are the producer depends on your sales channels and establishment."
}
```

`streams` is a first guess from the words in `products`, and `indicative` is always true. Confirm the streams with the client before you quote.

### 2. Read the France questions

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

It returns the three required answers, every stream with its lowest plan, and the plan rules. Ask the client these questions rather than guessing.

### 3. Get a quote

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

```json
{
  "ok": true,
  "custom_quote": false,
  "total": 977,
  "currency": "EUR",
  "lines": [
    { "country": "FR", "item": "France Standard", "eur": 380, "per": "year" },
    { "country": "DE", "item": "Germany packaging", "eur": 149, "per": "year" },
    { "country": "ES", "item": "Spain Essential", "eur": 199, "per": "year" },
    { "country": "ES", "item": "Spanish NIF", "eur": 249, "per": "one-off" }
  ],
  "france": { "plan": "Standard", "streams": ["household-packaging", "toys"], "stream_count": 2, "tonnes": "up_to_1", "why": [] },
  "germany": { "dual_system_estimate": { "eur": 121.43, "year": 2026, "vat_included": true, "charged": "in your client portal, before we accept the LUCID appointment" } },
  "note": "Indicative. Scheme fees and eco-contributions are billed separately at cost. Prices exclude VAT."
}
```

If `custom_quote` is true, the order is Enterprise: over 10 tonnes into France, or more than 3 French streams. `total` is null and only we can quote it.

### 4. Simulate the onboarding

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

```json
{
  "ok": true,
  "sandbox": true,
  "id": "sbx_...",
  "countries": [{
    "country": "DE",
    "status": "order_paid",
    "completed": ["order_paid"],
    "next": { "step": "mandate_signed", "client_action_required": true },
    "registration_number": null
  }],
  "complete": false
}
```

One step moves on every 5 seconds. France has 3 steps, Germany 5 and Spain 4, so Germany finishes after 20 seconds.

```bash
ID=$(curl -s -X POST https://epreurep.com/api/v1/sandbox/onboardings \
  -H 'content-type: application/json' -d '{"countries":["DE"]}' | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
sleep 25
curl -s https://epreurep.com/api/v1/sandbox/onboardings/$ID
```

```json
{
  "ok": true,
  "sandbox": true,
  "countries": [{
    "country": "DE",
    "status": "registered",
    "completed": ["order_paid", "mandate_signed", "dual_system_paid", "appointed_in_lucid", "registered"],
    "next": null,
    "registration_number": "DE-TEST-0000000000000"
  }],
  "complete": true,
  "note": "Simulated. No order, mandate, payment or registration exists. Test numbers are not valid anywhere."
}
```

Stop polling when `complete` is true. The id holds the whole state, so it keeps working and nothing is stored on our side.

### 5. Prepare the real link

```bash
curl -X POST https://epreurep.com/api/v1/onboarding-links \
  -H 'content-type: application/json' \
  -d '{"partner":"northstar","france":{"sells_to":"consumers","streams":["toys"],"tonnes":"up_to_1"},"company":"Example Ltd","email":"owner@example.com"}'
```

```json
{
  "ok": true,
  "custom_quote": false,
  "url": "https://epreurep.com/checkout/?fr=0&frs=toys&fr_seg=cons&ref=northstar#company=Example+Ltd&email=owner%40example.com",
  "note": "The client confirms the details, signs and pays at this link. Paid orders are tracked to the partner."
}
```

`northstar` is a demo code for testing. Partners use their own. Give the link to the person: company and email stay in the URL fragment, which never reaches a server.

## Rules for agents

- Never present a sandbox result as real. Numbers such as `DE-TEST-0000000000000` aren't valid anywhere.
- Never tell a client they are compliant or registered. Only a real register entry shows that.
- When `client_action_required` is true, stop and tell the person what they have to do.
- Don't sign, pay or appoint anyone on a client's behalf. A person does those.
- Treat `/check` as a first guess. Who the producer is depends on sales channels and establishment.
- Don't use a partner code that isn't yours.

## What only a person can do

| Country | Steps for the client personally |
|---|---|
| France | Sign the mandate, which can be done online |
| Germany | Sign the mandate by hand or with a qualified electronic signature, and name the representative in LUCID |
| Spain | Sign the mandate. The power of attorney is then verified, which can take time |

Our team does the registrations and declarations, so they run in days, not seconds. More in [why EPR registration can't be fully automated](/guides/why-epr-registration-cannot-be-fully-automated/).

## Errors

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

| Status | `error` | What to do |
|---|---|---|
| 400 | `invalid_json` | Send a JSON object |
| 400 | `invalid_request` | `message` names the field. Fix it and retry |
| 400 | `unknown_partner` | Use a real partner code |
| 404 | `not_found` | Check the path or the sandbox id |
| 405 | `method_not_allowed` | Use POST on this path |
| 429 |  | Wait, then retry |

## Prompt to give an agent

Paste this into any agent that can make HTTP requests.

```text
Test the EPR EU Rep API from start to finish.
1. Read https://epreurep.com/developers/ai-agents/index.md and https://epreurep.com/developers/openapi.json
2. POST /api/v1/check for a seller of cotton t-shirts in FR, DE and ES.
3. GET /api/v1/france/questionnaire and POST /api/v1/quote with the answers.
4. POST /api/v1/sandbox/onboardings for FR, DE and ES, then poll the id until complete is true.
5. Report each step's status, and which steps need the client personally.
It is a simulation: never present a test number as a real registration.
```

