Integrer gravemottak i eget system
For ledningseiere som vil hente og besvare gravemeldinger programmatisk - i stedet for via e-post eller dashbordet. Innboksen polles med et enkelt GET-kall, og svaret sendes med ett POST-kall.
Forutsetninger
- ✓Du har en Graveinfo-konto som er administrator for en ledningseier-organisasjon.
- ✓Organisasjonen er registrert som ledningseier i Graveinfo og mottar gravemeldinger via plattformen.
- ✓Du har et system som kan kjøre et periodisk jobb (cronjob, scheduler eller lignende).
Graveinfo har foreløpig ikke webhooks for ledningseiere. Polling av innboksen er mekanismen for å oppdage nye gravemeldinger.
Skaff en API-nøkkel på organisasjonen
Gå til Dashboard → API-nøkler på ledningseier-organisasjonen og opprett en ny nøkkel med scopene innboks:read (hente innboksen) og innboks:write (svare på gravemeldinger).
Viktig: Nøkkelen tilhører organisasjonen som opprettet den, og innboksen viser bare gravemeldinger der denne organisasjonen er mottaker. Scopes kan ikke endres etter opprettelse. Nøkkelen vises kun én gang - lagre den i en miljøvariabel:
export GRAVEINFO_API_KEY="gv_live_..."Hent ubesvarte gravemeldinger med GET /api/v1/innboks
Innboksen lister gravemeldinger der organisasjonen din er mottaker, nyeste først. Filtrer på status=pending for å bare få dem du ikke har svart på ennå (responded og all er de andre verdiene). Utkast vises ikke.
# Ubesvarte gravemeldinger, nyeste først
curl "https://graveinfo.no/api/v1/innboks?status=pending&limit=20" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"{
"data": [
{
"recipientId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"gravemelding": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"referenceNumber": "GI-2026-00042",
"title": "Graving Storgata 12",
"plannedStart": "2026-10-01",
"plannedEnd": "2026-10-14",
"location": {
"type": "Polygon",
"coordinates": [[
[10.7522, 59.9139],
[10.7535, 59.9139],
[10.7535, 59.9150],
[10.7522, 59.9150],
[10.7522, 59.9139]
]]
},
"workTypes": ["telekommunikasjon", "grunnarbeid"],
"description": "Legging av fiberkabel langs Storgata 12-18",
"contractor": {
"name": "Graving AS",
"orgnr": "987654321",
"contactPerson": "Ola Nordmann",
"phone": "91234567",
"email": "post@graving.no"
}
},
"status": "pending",
"receivedAt": "2026-09-15T10:00:01.000Z"
}
],
"meta": { "total": 1, "limit": 20, "offset": 0 }
}recipientIdID for mottakerrelasjonen - unik per gravemelding per mottaker. Det er denne du bruker når du svarer, ikke gravemelding.id.gravemeldingTittel, referansenummer, planlagt periode, arbeidstyper, beskrivelse og entreprenørens kontaktinfo. Felter kan være null hvis entreprenøren ikke har fylt dem ut.gravemelding.locationGraveområdet som GeoJSON-polygon i WGS84 (EPSG:4326), koordinater som [lengdegrad, breddegrad]. Skal du sjekke mot et eget register i UTM, må du transformere selv.statuspending til du har svart, deretter responded.receivedAtTidspunktet gravemeldingen ble sendt til dere. Bruk dette til å prioritere de eldste først.metatotal, limit og offset for paginering. limit er maks 100 - hent flere sider med offset hvis total er større.Rate limit: GET /innboks tillater 60 kall per minutt per nøkkel. Ved overskridelse svarer API-et med HTTP 429, kode RATE_LIMIT, og en Retry-After-header (i sekunder). Vent så lenge headeren sier før du prøver igjen. Ett kall hvert 5.-15. minutt er mer enn nok for de fleste.
Svar med POST /api/v1/innboks/{recipientId}/svar
Kroppen har ett påkrevd felt, response, og et valgfritt: note (fritekst til entreprenøren; legg en eventuell lenke til kartutsnitt her). Feltet attachmentUrl tas imot, men vises ikke for entreprenøren i dag:
RECIPIENT_ID=a1b2c3d4-5678-90ab-cdef-1234567890ab
curl -X POST "https://graveinfo.no/api/v1/innboks/$RECIPIENT_ID/svar" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"response": "conflict",
"note": "Vi har fiberkabler langs Storgata. Ring 22 00 00 00 for påvisning. Kart: https://kart.example.no/ledninger/storgata-12.pdf"
}'Svaret er HTTP 200:
{
"recipientId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"status": "responded",
"respondedAt": "2026-09-15T14:30:00.000Z"
}Verdien i response avgjør hva entreprenøren ser:
no_conflictDere har ingen infrastruktur i graveområdet. Entreprenøren trenger ikke ta hensyn til dere.conflictDere har infrastruktur i området. Entreprenøren må ta kontakt før graving - legg kontaktinfo og eventuelle krav i note. Gravemeldingen merkes med hasConflicts hos entreprenøren.needs_meetingDere trenger befaring eller mer informasjon før dere kan svare endelig. Bruk note til å si hva dere trenger.Et nytt svar overskriver det forrige. Kallet er ikke idempotent: et nytt POST på samme recipientId erstatter svaret som allerede er registrert, og varsler entreprenøren på nytt hvis note er satt. Du får HTTP 200, ikke 409. Svar derfor bare på elementer som fortsatt har status: "pending", med mindre du faktisk vil korrigere et tidligere svar.
Rate limit: 30 kall per minutt per nøkkel, med Retry-After på HTTP 429.
Sett opp en polling-loop
Et komplett eksempel som henter alle ubesvarte gravemeldinger (med paginering og håndtering av 429), sjekker hvert polygon mot ditt eget ledningsregister og svarer:
// Kjøres periodisk, f.eks. hvert 15. minutt fra en cronjob.
// Sjekk alltid GET /innboks?status=pending før du svarer, så du ikke
// overskriver svar som allerede er gitt manuelt i dashbordet.
const BASE_URL = 'https://graveinfo.no/api/v1';
const headers = { Authorization: `Bearer ${process.env.GRAVEINFO_API_KEY}` };
type InboxItem = {
recipientId: string;
gravemelding: {
id: string;
referenceNumber: string | null;
location: { type: 'Polygon'; coordinates: number[][][] } | null;
workTypes: string[];
};
status: 'pending' | 'responded';
receivedAt: string;
};
async function fetchPending(): Promise<InboxItem[]> {
const items: InboxItem[] = [];
let offset = 0;
while (true) {
const res = await fetch(`${BASE_URL}/innboks?status=pending&limit=100&offset=${offset}`, {
headers,
});
if (res.status === 429) {
const wait = Number(res.headers.get('Retry-After') ?? '60');
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (!res.ok) throw new Error(`GET /innboks feilet: ${res.status}`);
const { data, meta } = await res.json();
items.push(...data);
offset += data.length;
if (offset >= meta.total || data.length === 0) return items;
}
}
async function respond(recipientId: string, response: 'no_conflict' | 'conflict' | 'needs_meeting', note?: string) {
const res = await fetch(`${BASE_URL}/innboks/${recipientId}/svar`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify({ response, note }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Svar feilet for ${recipientId}: ${err.error.code}`);
}
}
export async function processInbox() {
const pending = await fetchPending();
for (const item of pending) {
// Din logikk: sjekk polygonet mot eget ledningsregister
const hit = await checkAgainstOwnRegister(item.gravemelding.location);
if (hit.hasInfrastructure) {
await respond(item.recipientId, 'conflict', hit.note);
} else {
await respond(item.recipientId, 'no_conflict');
}
}
console.log(`Behandlet ${pending.length} gravemeldinger`);
}
declare function checkAgainstOwnRegister(
location: InboxItem['gravemelding']['location'],
): Promise<{ hasInfrastructure: boolean; note?: string }>;Fordi status=pending bare returnerer det du ikke har svart på, trenger du ingen egen liste over behandlede ID-er - innboksen er tilstanden. Svar som gis manuelt i dashbordet forsvinner også fra pending.
Feilsøking
MISSING_ORGANIZATIONHTTP 403 - nøkkelen er ikke knyttet til en organisasjonLøsning: GET /innboks krever en nøkkel som tilhører en ledningseier-organisasjon (svar-endepunktet gir FORBIDDEN for samme tilfelle). En nøkkel opprettet på en entreprenørprofil uten tilhørende organisasjon fungerer ikke her. Opprett nøkkelen fra organisasjonen i dashbordet.
FORBIDDENHTTP 403 - manglende scope, eller mottakeren tilhører en annen organisasjonLøsning: Sjekk at nøkkelen har innboks:read (for GET /innboks) og innboks:write (for POST /svar). Får du FORBIDDEN på et svar med riktig scope, tilhører recipientId en annen organisasjon - bruk bare ID-er fra din egen innboks.
NOT_FOUNDHTTP 404 - ukjent recipientIdLøsning: recipientId er unik per gravemelding per mottaker. Bruk recipientId fra GET /innboks-responsen, ikke gravemelding.id.
BAD_REQUESTHTTP 400 - ugyldig JSON, ugyldig response eller ugyldig status-filterLøsning: response må være no_conflict, conflict eller needs_meeting. status-parameteren på GET /innboks må være pending, responded eller all. Sjekk at kroppen er gyldig JSON og at Content-Type er application/json.
RATE_LIMITHTTP 429 - for mange kallLøsning: GET /innboks tillater 60 og POST /svar 30 kall per minutt per nøkkel. Les Retry-After-headeren og vent så mange sekunder før neste forsøk. Reduser pollefrekvensen - nye gravemeldinger kommer sjelden oftere enn hvert kvarter.
Tom innboks? Innboksen viser bare gravemeldinger som er sendt til organisasjonen din. Sjekk med GET /api/v1/coverage?municipality=XXXX at organisasjonen er registrert i kommunene dere dekker, med gravemottak: "api".
Klar for full API-dokumentasjon?
Se API-referansen →