Filter und Takt
Nur relevante und neue Ausschreibungen abrufen, durch große Mengen blättern und im richtigen Takt abgleichen.
Diese Leistung erhalten Sie über ein individuelles Angebot.
Ein Lauf je Stunde hält Ihr System auf dem Stand von procuris. Filter bestimmen, welche Ausschreibungen Ihr System holt, zum Beispiel nur Bauleistungen in Bayern und Thüringen. Mit updated_since holt ein Lauf nur, was seit dem vorigen Lauf neu ist oder sich geändert hat.
Der Bestand bekommt höchstens einen neuen Stand je Stunde. Häufigere Abrufe holen einen neuen Stand früher ab, aber keine zusätzlichen Stände. lastModified zeigt, wann procuris eine Änderung übernommen hat, nicht wann die Vergabestelle sie veröffentlicht hat.
Vollständige Beispielanfrage
Die Beispielanfrage verbindet fünf Filter. Sie holt Bauleistungen in Bayern und Thüringen mit Angebotsfrist nach dem 1. Oktober und Auftragswert ab 100.000 Euro, geändert seit dem 24. September, 100 je Seite:
GET /v1/tenders?cpv=45000000&nuts=DE2,DEG&deadline_after=2026-10-01T00:00:00Z&value_min=100000&updated_since=2026-09-24T00:00:00Z&limit=100 HTTP/1.1
Host: api.procuris.eu
Authorization: Bearer <Ihr Token>
Accept: application/jsonHier passt ein einziger Datensatz. Darum ist nextCursor gleich null:
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 42{
"data": [
{
"id": "e9da30eb-50e6-47f1-887f-434016267b2f",
"status": "open",
"title": "Nordhausen - Sanierung Rolandbrunnen, Erneuerung Brunnentechnik",
"submissionDate": "2026-10-14T08:00:00.000Z",
"contractAmount": {
"value": 351000,
"currency": "EUR",
"estimated": false
},
"lastModified": "2026-09-24T09:10:00.000Z"
}
],
"nextCursor": null
}Das Beispiel zeigt nur sechs Felder. In der echten Antwort ist ein Eintrag in data ein vollständiger Datensatz wie im Datenmodell.
Filter
Filter sind Parameter an GET /v1/tenders. Verschiedene Filter gelten zusammen (und), während mehrere Werte in einem Filter wahlweise gelten (oder). cpv=45000000&nuts=DE2,DEG heißt also: Bauarbeiten, und zwar in Bayern oder Thüringen.
| Parameter | Wert | Beispiel | Wirkung |
|---|---|---|---|
cpv | CPV-Codes, durch Komma getrennt | 45000000,71000000 | Branche. Ein Code mit Nullen am Ende schließt alle Codes darunter ein. 45000000 umfasst also alle Bauarbeiten. Die CPV-Codes der Lose zählen mit. |
nuts | NUTS-Codes, durch Komma getrennt | DE2,DEG | Region. Ein Code schließt die Gebiete darunter ein. DE2 steht für Bayern, DEG für Thüringen. |
published_since | Zeitpunkt nach ISO 8601 | 2026-09-01T00:00:00Z | nur Bekanntmachungen, die ab diesem Zeitpunkt veröffentlicht wurden |
updated_since | Zeitpunkt nach ISO 8601 | 2026-09-24T00:00:00Z | nur Datensätze, deren lastModified ab diesem Zeitpunkt liegt |
deadline_after | Zeitpunkt nach ISO 8601 | 2026-10-01T00:00:00Z | nur Ausschreibungen, deren Angebotsfrist danach liegt |
value_min, value_max | Betrag in Euro ohne Umsatzsteuer, ganze Zahl | 100000 | Auftragswert von, bis. Geschätzte Werte zählen mit. Beträge in anderen Währungen fallen heraus. |
procedure | eForms-Verfahrensart, durch Komma getrennt | open,restricted | nur diese Verfahrensarten |
archived | true oder false | true | mit archivierten Ausschreibungen. Ohne Angabe: ohne. Zusammen mit updated_since übergeht die API archived ohne Fehler, weil der Abgleich archivierte Datensätze dann ohnehin liefert. |
limit | Zahl von 1 bis 100 | 100 | Datensätze je Seite, ohne Angabe 50 |
Fehlende Werte. Ein Filter auf ein Feld schließt Ausschreibungen aus, bei denen das Feld fehlt. Mit nuts fallen Ausschreibungen ohne Region heraus, mit deadline_after solche ohne Angebotsfrist, mit value_min oder value_max solche ohne Auftragswert. Wollen Sie Ausschreibungen ohne Region nicht verpassen, rufen Sie ohne nuts ab und ordnen Sie die Region in Ihrem System zu, zum Beispiel über realizedLocation.
Nur Neues und Geändertes holen
updated_since holt nur Änderungen seit dem vorigen Lauf. Ihr System setzt den Parameter auf das größte lastModified des vorigen Laufs. Liefert ein Lauf keinen Datensatz, bleibt der gespeicherte Stand unverändert. Setzen Sie dann nicht die Uhrzeit Ihres Systems ein, weil sie von der Zeit bei procuris abweichen kann und Ihr System sonst Änderungen überspringt. Mit dem gespeicherten Stand bekommt es die Änderungen seitdem, soweit sie zu den Filtern passen:
- neue Ausschreibungen, die zu den Filtern passen
- geänderte Ausschreibungen, zum Beispiel mit verschobener Frist oder Berichtigung, mit derselben
id - archivierte Ausschreibungen, auch ohne
archived=true, mitstatusundarchiveReason
Die id unterscheidet Neues von Geändertem. Eine id, die Ihr System nicht kennt, ist eine neue Ausschreibung. Eine bekannte id zeigt eine Änderung.
Der Stand erfasst jede Änderung, wenn Ihr System ihn am Laufende speichert. lastModified ist der Zeitpunkt der letzten inhaltlichen Änderung bei procuris und steigt nur. Ein neues checkedAt allein ist keine inhaltliche Änderung. Die Liste ist nach lastModified aufsteigend sortiert, bei gleichem Zeitpunkt nach id. updated_since schließt den genannten Zeitpunkt ein. Ein Lauf ab dem größten lastModified des vorigen Laufs erfasst darum jede Änderung seitdem. Alle Datensätze mit genau diesem größten lastModified bekommt Ihr System so ein zweites Mal. Das Anlegen oder Aktualisieren über id fängt das ab.
Herausfallen aus Filtern. Ein gefilterter Abgleich verliert Ausschreibungen, die aus den Filtern fallen. Korrigiert die Vergabestelle zum Beispiel die Region, passt die Ausschreibung nicht mehr zu Ihren inhaltlichen Filtern und kommt im gefilterten Abgleich nicht mehr vor. Ihr System behält dann den alten Stand. Brauchen Sie einen vollständigen Abgleich, rufen Sie ohne inhaltliche Filter ab, nur mit updated_since, und filtern Sie in Ihrem System. Das kostet mehr Abrufe, weil Ihr System dann jede geänderte Ausschreibung des Bestands holt.
Seiten
Große Ergebnismengen kommen in Seiten zu höchstens 100 Datensätzen. Für die nächste Seite hängen Sie cursor=<nextCursor> an denselben Abruf mit denselben Filtern. Ist nextCursor gleich null, ist die Liste zu Ende. Ein Cursor gilt 24 Stunden. Ihr System speichert ihn nur für den laufenden Lauf und wertet seinen Inhalt nicht aus, weil sich sein Aufbau ändern kann.
Ein abgelaufener Cursor führt zur Antwort 400. Der Antworttext nennt den Grund im Format application/problem+json:
{
"type": "https://docs.procuris.eu/docs/api/zugang#fehler",
"title": "Cursor abgelaufen",
"status": 400,
"detail": "Der Cursor ist älter als 24 Stunden.",
"instance": "/v1/tenders"
}Danach beginnt Ihr System den Lauf neu. Es setzt updated_since auf den gespeicherten Stand. Datensätze, die es im abgebrochenen Lauf schon bekommen hat, kommen erneut und werden über id aktualisiert.
Takt und Grenzen
| Frage | Antwort |
|---|---|
| Wie oft ändern sich die Daten? | höchstens ein neuer Stand je Stunde. lastModified zeigt, wann procuris eine Änderung übernommen hat. |
| Wie oft ruft Ihr System am besten ab? | einmal je Stunde mit updated_since. Häufigere Abrufe holen einen neuen Stand früher ab, aber keine zusätzlichen Stände. |
| Wie oft darf es abrufen? | 60 Abrufe je Minute je Organisation, über beide Tokens zusammen. Ihr Angebot kann höhere Grenzen umfassen. |
| Was passiert darüber? | Antwort 429 mit Retry-After in Sekunden |
| Wie lange dauert die Erstbefüllung? | Bei 100 Datensätzen je Seite und 60 Abrufen je Minute holt Ihr System bis zu 6.000 Datensätze je Minute. 30.000 Datensätze dauern also rund 5 Minuten. Mit archived=true kommt zusätzlich das Archiv dazu, das bis Januar 2022 zurückreicht. |
Der stündliche Abgleich in Schritten
- Erster Lauf: ohne
updated_since, mit Ihren Filtern undlimit=100. Ihr System blättert übernextCursorbis zum Ende und legt jeden Datensatz überidan. - Stand merken: das größte
lastModifieddes Laufs speichern. - Jede Stunde: derselbe Abruf mit
updated_sinceauf dem gespeicherten Stand. Jeden Datensatz überidanlegen oder aktualisieren. Archivierte mitexpired,cancelledoderawardedin Ihrem System abschließen. Archivierte mitmergedmit dem Datensatz ausmergedIntozusammenführen. Danach das größtelastModifieddieses Laufs als neuen Stand speichern. Kam kein Datensatz, bleibt der alte Stand, nie die eigene Uhrzeit. - Bei 429 oder 503: so lange warten, wie
Retry-Aftersagt, dann denselben Abruf wiederholen. - Bei 500, 502 oder 504: nach 1, 2, 4, 8 und 16 Minuten erneut versuchen. Scheitert auch das, bricht der Lauf ab, ohne den Stand zu ändern. Der nächste stündliche Lauf setzt am alten Stand wieder an.
- Läuft der vorige Lauf noch: Ist zur vollen Stunde der vorige Lauf nicht beendet, zum Beispiel weil er auf Wiederholungen wartet, lässt Ihr System den neuen Lauf aus. So schreiben nie zwei Läufe gleichzeitig, und der Stand bleibt eindeutig.
Verwandte Seiten
Angebot anfordern
Schildern Sie uns, was Sie nutzen oder anbinden möchten. Ihr Angebot richtet sich nach diesem Umfang.
Datenmodell
Die Felder eines Datensatzes mit Typ, wie fehlende und geschätzte Werte aussehen und welcher Schlüssel eine Ausschreibung dauerhaft kennzeichnet.
Webhooks
procuris meldet neue Ausschreibungen und Treffer von sich aus an Ihr System. Wann Webhooks statt der API passen und wie Ihre IT die erste Nachricht empfängt.