UdiWatch

API reference

Build on the UdiWatch API.

Watch public EUDAMED UDI-DI and manufacturer SRN records. Search, store a baseline, and read a diff when a watched field changes. Each route has its own page.

Not an official European Commission service. Records can be missing, late, or wrong because the upstream register is. Do not treat a response as a certificate of conformity.

Quickstart

Create an account in the app, or with POST /v1/trial. The first response contains the only copy of the API key. Store it. A later login returns a session, not the key.

BASE=https://udiwatch.com

curl -sS -X POST "$BASE/v1/trial" \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"at-least-8"}'

export UW_KEY='uw_…'

curl -sS "$BASE/v1/search?q=16977660098955"
curl -sS -X POST "$BASE/v1/watches" \
  -H "X-API-Key: $UW_KEY" -H 'Content-Type: application/json' \
  -d '{"subject":"16977660098955"}'
curl -sS -X POST "$BASE/v1/sync" \
  -H "X-API-Key: $UW_KEY" -H 'Content-Type: application/json' \
  -d '{}'

The same routes answer on https://udiwatch.com, https://app.udiwatch.com, and https://docs.udiwatch.com. Use one host and stay on it. A POST with no body is rejected upstream with 403, so send {} when you have no fields.

Conventions

  • JSON in, JSON out, UTF-8. Send Content-Type: application/json on POST.
  • Times are ISO-8601 UTC.
  • A subject that matches CC-XX-digits (for example AT-MF-000000252) is an SRN. Anything else is a UDI-DI. Spaces are stripped. SRNs are uppercased.
  • CORS is open on /v1/* and /mcp.
  • There is no /v2. The live contract is openapi.json.

Authentication

Two credentials exist. They are not interchangeable.

CredentialHeaderActs as
API key uw_…X-API-Key or Authorization: Bearer uw_…The account. Can manage users, keys, and the webhook.
Session us_…Authorization: Bearer us_…One user. An owner can manage the account. A member can use watches, not users, keys, or the webhook.

Public reads need no credential: search, one device, one actor, OpenAPI, health, and MCP initialize / tools/list / device search tools.

Errors

Error bodies are {"error": "…"}. The message is the one you should show. Do not match on it if a status code exists.

StatusWhen
400Bad email, short password, empty subject, webhook that is not public https, bad id.
401Missing or unknown key or session. Login with a wrong password.
402The period has ended. Sync and new watches stop. A cancel during an open trial does not end access early.
403A member tried to manage users, keys, or the webhook.
404Unknown path, or a UDI that is not in the public rows we could read.
409Watch cap, user cap, or API-key cap.
429Rate limit.

Limits

LimitValue
Trial creates8 per IP per hour
Logins20 per IP per hour
Search40 per IP per minute
MCP60 calls per IP per minute
Watches25 on a trial
Users and active API keys10 each
CSV import500 lines, still inside the watch cap
Audit trailNewest 100 on REST, newest 50 on MCP list_changes

Watches and diffs

A watch is one UDI-DI or one SRN on one account. Adding it reads the register immediately and stores that snapshot. That first read is a baseline and does not create an event. The next successful read compares fields and writes an event only when something changed.

  • A new value that is empty does not count as a change.
  • If the new read failed, certificate diffs are skipped. A timeout is not “certificate removed”.
  • No expiry in the source means no expiry in the payload.
  • Watching the same subject again does not reset the baseline.

Questions about a key: [email protected].