Kodeeksempler
Kopier og tilpass eksemplene under. Alle eksempler forutsetter at API-nøkkelen er lagret i miljøvariabelen GRAVEINFO_API_KEY. Nøkkelen opprettes under API-nøkler i dashbordet, og må ha riktige scopes for endepunktene du bruker.
Organisasjonssøk
Søk etter ledningseiere og entreprenører. Krever ikke scope. Se full referanse →
curl
# Søk etter ledningseiere med navn (nøkkel er valgfri, men gir høyere rate limit)
curl "https://graveinfo.no/api/v1/organizations/search?q=Elvia&type=owner" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Oppslag på organisasjonsnummer
curl "https://graveinfo.no/api/v1/organizations/search?orgnr=920415523" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Filtrer på kommune (0301 = Oslo)
curl "https://graveinfo.no/api/v1/organizations/search?q=vann&municipality=0301&limit=10" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Paginering - side 2 med 20 resultater per side
curl "https://graveinfo.no/api/v1/organizations/search?q=nett&limit=20&offset=20" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"JavaScript / TypeScript
Fungerer i Node.js (18+), Deno og i nettleseren. Ingen eksterne avhengigheter. BASE_URL og GRAVEINFO_API_KEY herfra brukes i alle eksemplene under.
const GRAVEINFO_API_KEY = process.env.GRAVEINFO_API_KEY;
const BASE_URL = 'https://graveinfo.no/api/v1';
async function searchOrganizations(query: string, options?: {
type?: 'owner' | 'contractor' | 'all';
municipality?: string;
limit?: number;
offset?: number;
}) {
const params = new URLSearchParams({ q: query });
if (options?.type) params.set('type', options.type);
if (options?.municipality) params.set('municipality', options.municipality);
if (options?.limit) params.set('limit', String(options.limit));
if (options?.offset) params.set('offset', String(options.offset));
const response = await fetch(`${BASE_URL}/organizations/search?${params}`, {
headers: {
'Authorization': `Bearer ${GRAVEINFO_API_KEY}`,
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
// Eksempel: søk etter ledningseiere i Oslo
const result = await searchOrganizations('vann', {
type: 'owner',
municipality: '0301',
});
console.log(`Fant ${result.meta.total} resultater`);
result.data.forEach((org) => {
console.log(`${org.name} (${org.orgnr}) - gravemottak: ${org.gravemottak}`);
});Paginering
Bruk en async generator for å hente alle sider automatisk:
async function* paginateOrganizations(query: string, pageSize = 20) {
let offset = 0;
let total = Infinity;
while (offset < total) {
const result = await searchOrganizations(query, { limit: pageSize, offset });
total = result.meta.total;
yield* result.data;
offset += pageSize;
}
}
// Hent alle treff for "nett"
for await (const org of paginateOrganizations('nett')) {
console.log(org.name);
}Python
Krever requests (pip install requests). HEADERS herfra brukes i alle Python-eksemplene under.
import os
import requests
GRAVEINFO_API_KEY = os.environ["GRAVEINFO_API_KEY"]
BASE_URL = "https://graveinfo.no/api/v1"
HEADERS = {"Authorization": f"Bearer {GRAVEINFO_API_KEY}"}
def search_organizations(query: str, **kwargs) -> dict:
"""Søk etter organisasjoner i Graveinfo."""
params = {"q": query, **kwargs}
response = requests.get(
f"{BASE_URL}/organizations/search",
params=params,
headers=HEADERS,
timeout=10,
)
response.raise_for_status()
return response.json()
# Eksempel: søk etter ledningseiere i Bergen (4601)
result = search_organizations("vann", type="owner", municipality="4601", limit=10)
print(f"Fant {result['meta']['total']} resultater")
for org in result["data"]:
print(f" {org['name']} ({org['orgnr']}) - {org['gravemottak']}")Paginering
def paginate_organizations(query: str, page_size: int = 20):
"""Generator som henter alle sider for et søk."""
offset = 0
while True:
result = search_organizations(query, limit=page_size, offset=offset)
yield from result["data"]
offset += page_size
if offset >= result["meta"]["total"]:
break
# Hent alle "nett"-organisasjoner
for org in paginate_organizations("nett"):
print(org["name"])Feilhåndtering
import requests
try:
result = search_organizations("elvia")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
print("Rate limit nådd - vent ett minutt")
elif e.response.status_code == 401:
print("Ugyldig API-nøkkel")
else:
error = e.response.json()
print(f"API-feil: {error['error']['message']}")Infrastrukturdekning per kommune
Finn alle ledningseiere registrert i en kommune. Krever nøkkel, men ikke scope. Se full referanse →
curl
# Finn alle ledningseiere i Oslo (0301)
curl "https://graveinfo.no/api/v1/coverage?municipality=0301" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Filtrer på infrastrukturtype
curl "https://graveinfo.no/api/v1/coverage?municipality=0301&type=vann_avlop" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"JavaScript / TypeScript
async function getCoverage(municipalityNumber: string, type?: string) {
const params = new URLSearchParams({ municipality: municipalityNumber });
if (type) params.set('type', type);
const response = await fetch(
`${BASE_URL}/coverage?${params}`,
{ headers: { 'Authorization': `Bearer ${GRAVEINFO_API_KEY}` } },
);
if (!response.ok) {
const error = await response.json();
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
// Vis alle eiere i en kommune
const coverage = await getCoverage('0301');
console.log(`${coverage.municipality.name}: ${coverage.total} ledningseiere`);
for (const owner of coverage.owners) {
const types = owner.infrastructureTypes.join(', ');
console.log(` ${owner.name} - ${types} (${owner.gravemottak})`);
}Python
def get_coverage(municipality_number: str, infra_type: str | None = None) -> dict:
"""Hent ledningseiere for en gitt kommune."""
params: dict = {"municipality": municipality_number}
if infra_type:
params["type"] = infra_type
response = requests.get(
f"{BASE_URL}/coverage",
params=params,
headers=HEADERS,
timeout=10,
)
response.raise_for_status()
return response.json()
# Bygg en oversikt over flere kommuner
municipalities = ["0301", "4601", "1103"] # Oslo, Bergen, Stavanger
for mun_num in municipalities:
coverage = get_coverage(mun_num)
mun_name = coverage["municipality"]["name"]
count = coverage["total"]
print(f"{mun_name} ({mun_num}): {count} ledningseiere")Opprett og send gravemelding
For entreprenører. Krever scope gravemeldinger:write. Se full referanse → eller steg-for-steg-guiden →
To steg: POST /gravemeldinger oppretter et utkast og returnerer hvilke ledningseiere som ble funnet. POST /gravemeldinger/{id}/send sender det. Sett autoSend: true for å gjøre begge i ett kall. Polygonet er GeoJSON i WGS84 med [lengdegrad, breddegrad]. Eksemplene under bruker en liten firkant i Oslo sentrum.
curl
# 1. Opprett utkast - polygon i WGS84 [lengdegrad, breddegrad]
curl -X POST "https://graveinfo.no/api/v1/gravemeldinger" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Graving Storgata 12",
"description": "Legging av fiberkabel langs Storgata 12-18",
"workTypes": ["telekommunikasjon", "grunnarbeid"],
"location": {
"type": "Polygon",
"coordinates": [[
[10.7522, 59.9139],
[10.7535, 59.9139],
[10.7535, 59.9150],
[10.7522, 59.9150],
[10.7522, 59.9139]
]]
},
"plannedStart": "2026-10-01",
"plannedEnd": "2026-10-14",
"contactPerson": { "name": "Ola Nordmann", "phone": "91234567" }
}'
# Svaret har "status": "draft", "referenceNumber", "recipients" og "_links.send"
# 2. Send utkastet (ingen body) - bruk id fra svaret over
curl -X POST "https://graveinfo.no/api/v1/gravemeldinger/3fa85f64-5717-4562-b3fc-2c963f66afa6/send" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Alternativ: opprett og send i ett kall med "autoSend": true
curl -X POST "https://graveinfo.no/api/v1/gravemeldinger" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "...", "location": { ... }, "plannedStart": "2026-10-01",
"plannedEnd": "2026-10-14", "workTypes": ["veiarbeid"],
"contactPerson": { "name": "Ola Nordmann", "phone": "91234567" },
"autoSend": true }'JavaScript / TypeScript
type WorkType =
| 'telekommunikasjon' | 'vann_avlop' | 'hoyspenning' | 'lavspenning' | 'veiarbeid'
| 'grunnarbeid' | 'fjernvarme' | 'annet' | 'pressing' | 'boring';
interface GravemeldingInput {
title: string;
location: { type: 'Polygon'; coordinates: number[][][] }; // WGS84 [lng, lat]
plannedStart: string; // YYYY-MM-DD, i dag eller senere
plannedEnd: string; // YYYY-MM-DD, samme dag som eller etter plannedStart
workTypes: WorkType[];
contactPerson: { name: string; phone: string; email?: string };
description?: string;
autoSend?: boolean;
}
async function createGravemelding(input: GravemeldingInput) {
const response = await fetch(`${BASE_URL}/gravemeldinger`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${GRAVEINFO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(input),
});
if (!response.ok) {
const error = await response.json();
// Ved VALIDATION_ERROR ligger feilmelding per felt i error.error.fields
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
async function sendGravemelding(id: string) {
const response = await fetch(`${BASE_URL}/gravemeldinger/${id}/send`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${GRAVEINFO_API_KEY}` },
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
// 1. Opprett utkast - polygon i WGS84 [lengdegrad, breddegrad]
const draft = await createGravemelding({
title: 'Graving Storgata 12',
description: 'Legging av fiberkabel langs Storgata 12-18',
workTypes: ['telekommunikasjon', 'grunnarbeid'],
location: {
type: 'Polygon',
coordinates: [[
[10.7522, 59.9139],
[10.7535, 59.9139],
[10.7535, 59.9150],
[10.7522, 59.9150],
[10.7522, 59.9139],
]],
},
plannedStart: '2026-10-01',
plannedEnd: '2026-10-14',
contactPerson: { name: 'Ola Nordmann', phone: '91234567' },
});
console.log(`Utkast ${draft.referenceNumber} opprettet (status: ${draft.status})`);
console.log(`${draft.recipients.length} ledningseiere funnet i området`);
console.log(`Send med POST ${draft._links.send}`);
if (draft.recipients.length === 0) {
throw new Error('Ingen ledningseiere i området - /send vil feile med NO_RECIPIENTS_FOUND');
}
// 2. Send utkastet
const sent = await sendGravemelding(draft.id);
console.log(`Sendt ${sent.sentAt} - status: ${sent.status}`);Med autoSend
Med autoSend: true returneres /send-svaret direkte. Feiler sendingen, får du utkastet tilbake med _warnings:
// Opprett og send i ett kall
const result = await createGravemelding({ ...input, autoSend: true });
if (result.status === 'sent') {
console.log(`Sendt! Ref: ${result.referenceNumber}, ${result.recipients.length} mottakere`);
} else if (result._warnings) {
// Utkastet finnes, men sendingen feilet - kall /send på nytt senere
console.warn(`Utkast ${result.referenceNumber} lagret, men ikke sendt:`);
console.warn(result._warnings[0].detail?.error?.code);
}Python
def create_gravemelding(
title: str,
work_types: list[str],
polygon_coords: list[list[list[float]]],
planned_start: str,
planned_end: str,
contact_name: str,
contact_phone: str,
description: str | None = None,
auto_send: bool = False,
) -> dict:
"""Opprett en gravemelding som utkast.
polygon_coords: lukket ring i WGS84 (EPSG:4326), [lengdegrad, breddegrad].
"""
payload = {
"title": title,
"workTypes": work_types,
"location": {"type": "Polygon", "coordinates": polygon_coords},
"plannedStart": planned_start,
"plannedEnd": planned_end,
"contactPerson": {"name": contact_name, "phone": contact_phone},
"autoSend": auto_send,
}
if description:
payload["description"] = description
response = requests.post(
f"{BASE_URL}/gravemeldinger",
json=payload,
headers=HEADERS,
timeout=30,
)
if response.status_code == 400:
# VALIDATION_ERROR har feilmelding per felt i error["fields"]
print(response.json()["error"])
response.raise_for_status()
return response.json()
def send_gravemelding(gravemelding_id: str) -> dict:
"""Send et utkast til mottakerne. Ingen body."""
response = requests.post(
f"{BASE_URL}/gravemeldinger/{gravemelding_id}/send",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
return response.json()
# 1. Opprett utkast - liten firkant i Oslo sentrum, WGS84 [lng, lat]
coords = [[
[10.7522, 59.9139],
[10.7535, 59.9139],
[10.7535, 59.9150],
[10.7522, 59.9150],
[10.7522, 59.9139],
]]
draft = create_gravemelding(
title="Graving Storgata 12",
work_types=["telekommunikasjon", "grunnarbeid"],
polygon_coords=coords,
planned_start="2026-10-01",
planned_end="2026-10-14",
contact_name="Ola Nordmann",
contact_phone="91234567",
description="Legging av fiberkabel langs Storgata 12-18",
)
print(f"Utkast {draft['referenceNumber']} opprettet (status: {draft['status']})")
print(f"{len(draft['recipients'])} ledningseiere funnet i området")
print(f"Send med POST {draft['_links']['send']}")
# 2. Send utkastet
if draft["recipients"]:
sent = send_gravemelding(draft["id"])
print(f"Sendt {sent['sentAt']} - status: {sent['status']}")
else:
print("Ingen ledningseiere i området - /send vil feile med NO_RECIPIENTS_FOUND")List og hent gravemeldinger
Les egne gravemeldinger og sjekk mottakerstatus. Krever scope gravemeldinger:read. Se full referanse →
curl
# List alle gravemeldinger
curl "https://graveinfo.no/api/v1/gravemeldinger" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Filtrer på status
curl "https://graveinfo.no/api/v1/gravemeldinger?status=sent" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Hent én gravemelding med mottakerstatus og kartblad-lenker
curl "https://graveinfo.no/api/v1/gravemeldinger/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"JavaScript / TypeScript
async function listGravemeldinger(options?: {
status?: 'draft' | 'sent' | 'in_progress' | 'completed';
from?: string;
to?: string;
limit?: number;
offset?: number;
}) {
const params = new URLSearchParams();
if (options?.status) params.set('status', options.status);
if (options?.from) params.set('from', options.from);
if (options?.to) params.set('to', options.to);
if (options?.limit) params.set('limit', String(options.limit));
if (options?.offset) params.set('offset', String(options.offset));
const url = `${BASE_URL}/gravemeldinger${params.toString() ? '?' + params : ''}`;
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${GRAVEINFO_API_KEY}` },
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
async function getGravemelding(id: string) {
const response = await fetch(`${BASE_URL}/gravemeldinger/${id}`, {
headers: { 'Authorization': `Bearer ${GRAVEINFO_API_KEY}` },
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
// Hent alle som venter på svar, og vis konflikter og kartblad
const { data } = await listGravemeldinger({ status: 'sent' });
for (const gm of data) {
console.log(`${gm.referenceNumber} sendt ${gm.sentAt}: ${gm.summary.pending} venter`);
if (gm.summary.hasConflicts) {
const detail = await getGravemelding(gm.id);
const conflicts = detail.recipients.filter((r) => r.response === 'conflict');
console.log(` ${conflicts.length} konflikter`);
for (const r of conflicts) {
console.log(` - ${r.name}: ${r.responseNote}`);
// kartbladUrl er en signert lenke som er gyldig i én time
if (r.hasKartblad) console.log(` Kartblad: ${r.kartbladUrl}`);
}
}
}Innboks og svar for ledningseiere
Hent mottatte gravemeldinger og registrer svar. Krever scope innboks:read og innboks:write. Se full referanse →
curl
# Ubesvarte gravemeldinger i innboksen
curl "https://graveinfo.no/api/v1/innboks?status=pending" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY"
# Svar på en gravemelding - recipientId fra innboksen
curl -X POST "https://graveinfo.no/api/v1/innboks/a1b2c3d4-5678-90ab-cdef-1234567890ab/svar" \
-H "Authorization: Bearer $GRAVEINFO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "response": "no_conflict", "note": "Ingen ledninger i dette området." }'JavaScript / TypeScript
async function getInnboks(status: 'pending' | 'responded' | 'all' = 'pending') {
const params = new URLSearchParams({ status });
const response = await fetch(`${BASE_URL}/innboks?${params}`, {
headers: { 'Authorization': `Bearer ${GRAVEINFO_API_KEY}` },
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error ${response.status}: ${error.error?.message}`);
}
return response.json();
}
async function respondToGravemelding(
recipientId: string,
response: 'no_conflict' | 'conflict' | 'needs_meeting',
note?: string,
) {
const res = await fetch(`${BASE_URL}/innboks/${recipientId}/svar`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${GRAVEINFO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ response, note }),
});
if (!res.ok) {
const error = await res.json();
throw new Error(`API error ${res.status}: ${error.error?.message}`);
}
return res.json();
}
// Vis ubesvarte gravemeldinger
const { data } = await getInnboks('pending');
for (const item of data) {
const gm = item.gravemelding;
console.log(`${gm.referenceNumber}: ${gm.title} (${gm.workTypes.join(', ')})`);
console.log(` ${gm.plannedStart} - ${gm.plannedEnd}`);
console.log(` Entreprenør: ${gm.contractor?.name}, ${gm.contractor?.phone}`);
// gm.location er et GeoJSON Polygon i WGS84 - sjekk mot eget ledningsnett her
}
// Svar på den første
if (data.length > 0) {
const svar = await respondToGravemelding(
data[0].recipientId,
'no_conflict',
'Ingen ledninger i dette området.',
);
console.log(`Svar registrert ${svar.respondedAt} (status: ${svar.status})`);
}Python
def get_innboks(status: str = "pending", limit: int = 20, offset: int = 0) -> dict:
"""Hent gravemeldinger organisasjonen er mottaker av."""
response = requests.get(
f"{BASE_URL}/innboks",
params={"status": status, "limit": limit, "offset": offset},
headers=HEADERS,
timeout=10,
)
response.raise_for_status()
return response.json()
def respond_to_gravemelding(
recipient_id: str,
response: str, # "no_conflict", "conflict" eller "needs_meeting"
note: str | None = None,
) -> dict:
"""Registrer svar på en mottatt gravemelding."""
payload: dict = {"response": response}
if note:
payload["note"] = note
res = requests.post(
f"{BASE_URL}/innboks/{recipient_id}/svar",
json=payload,
headers=HEADERS,
timeout=10,
)
res.raise_for_status()
return res.json()
# Gå gjennom ubesvarte gravemeldinger
innboks = get_innboks(status="pending")
for item in innboks["data"]:
gm = item["gravemelding"]
contractor = gm["contractor"] or {}
print(f"{gm['referenceNumber']}: {gm['title']} ({', '.join(gm['workTypes'])})")
print(f" {gm['plannedStart']} - {gm['plannedEnd']}, {contractor.get('name')}")
# gm["location"] er et GeoJSON Polygon i WGS84 - sjekk mot eget ledningsnett
svar = respond_to_gravemelding(
item["recipientId"],
"conflict",
note="Vi har kabler i området. Ta kontakt for påvisning.",
)
print(f" Svar registrert {svar['respondedAt']}")Sett miljøvariabelen
export GRAVEINFO_API_KEY="gv_live_..."GRAVEINFO_API_KEY=gv_live_...require("dotenv").config()