Et versjonert REST-API for å lese og skrive selskapets data, og webhooks som varsler deg når noe skjer. Alle endepunkter er dokumentert her.
- Gå til Innstillinger → Integrasjoner
- Opprett et token med bare de tilgangene integrasjonen trenger
- Velg levetid — tolv måneder er standard
- Kopier tokenet — det vises bare én gang
Send tokenet som Authorization-header på hvert kall:
curl https://konradoffice.no/api/v1/contacts \
-H "Authorization: Bearer kon_xxxxxxxxxxxxxxxx" \
-H "Accept: application/json"
Tokenet avgjør hvilket selskap kallet handler på vegne av. Det finnes ingen parameter for å bytte selskap, og et token når aldri data utenfor sitt eget.
Bare eier og daglig leder kan opprette tokens. Alle svar er JSON, og alle datoer er ISO 8601.
Alt ligger under /api/v1. Versjonen står i stien fra første dag, så en endring som bryter noe er noe man velger å ta i bruk, ikke noe man våkner til.
| Metode | Sti | Tilgang | Gjør |
|---|---|---|---|
| GET | /api/v1/contacts | contacts:read | Liste kontakter |
| GET | /api/v1/contacts/{id} | contacts:read | Hente én kontakt |
| POST | /api/v1/contacts | contacts:write | Opprette kontakt |
| GET | /api/v1/products | products:read | Liste produkter |
| GET | /api/v1/products/{id} | products:read | Hente ett produkt |
| POST | /api/v1/products | products:write | Opprette produkt |
| GET | /api/v1/invoices | invoices:read | Liste fakturaer |
| GET | /api/v1/invoices/{id} | invoices:read | Hente én faktura med linjer |
Disse tilgangene finnes også, men har ingen endepunkter ennå: vouchers:read og webhooks:manage.
GET /api/v1/contacts
Krever contacts:read. Parametre:
| Parameter | Betydning |
|---|---|
type | customer, supplier eller both |
search | Fritekst mot navn |
updated_since | Bare kontakter endret etter dette tidspunktet |
per_page | 1–100, standard 25 |
{
"data": [
{
"id": 12,
"contact_number": "CON20260012",
"type": "customer",
"is_private": false,
"name": "Bakeriet AS",
"organization_number": "123456789",
"email": "post@bakeriet.no",
"phone": "22334455",
"address": {
"street": "Storgata 1",
"postal_code": "0155",
"city": "Oslo",
"country": "NO"
},
"payment_terms_days": 14,
"is_active": true,
"created_at": "2026-08-30T09:15:00+02:00",
"updated_at": "2026-08-30T09:15:00+02:00"
}
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "per_page": 25, "total": 143 }
}
GET /api/v1/contacts/{id}
Krever contacts:read. Samme felter, uten data-liste. Kontakter i andre selskaper gir 404.
POST /api/v1/contacts
Krever contacts:write.
| Felt | Krav | Merknad |
|---|---|---|
name | Påkrevd | Maks 255 tegn |
type | Påkrevd | customer, supplier eller both |
organization_number | Valgfri | Nøyaktig ni siffer |
email | Valgfri | Gyldig e-postadresse |
phone | Valgfri | Maks 30 tegn |
address, postal_code, city | Valgfri | |
country | Valgfri | To bokstaver, standard NO |
payment_terms_days | Valgfri | 0–365, standard 14 |
curl -X POST https://konradoffice.no/api/v1/contacts \
-H "Authorization: Bearer kon_..." \
-H "Content-Type: application/json" \
-d '{"name":"Bakeriet AS","type":"customer","organization_number":"123456789"}'
Svarer 201 med den opprettede kontakten. Kontaktnummer tildeles automatisk. Utløser webhooken contact.created.
GET /api/v1/products
Krever products:read. Parametre:
| Parameter | Betydning |
|---|---|
search | Fritekst mot navn |
active_only | true utelater deaktiverte produkter |
updated_since | Bare produkter endret etter dette tidspunktet |
per_page | 1–100, standard 25 |
{
"data": [
{
"id": 7,
"sku": "KAFFE-500",
"name": "Kaffe 500 g",
"description": "Mørkbrent",
"price": 129.00,
"cost_price": 74.50,
"is_stocked": true,
"is_active": true,
"created_at": "2026-08-30T09:15:00+02:00",
"updated_at": "2026-08-30T09:15:00+02:00"
}
]
}
GET /api/v1/products/{id}
Krever products:read.
POST /api/v1/products
Krever products:write.
| Felt | Krav | Merknad |
|---|---|---|
name | Påkrevd | Maks 255 tegn |
price | Påkrevd | Tall, minst 0 |
sku | Valgfri | Må være ledig i selskapet |
description | Valgfri | Maks 1000 tegn |
cost_price | Valgfri | Tall, minst 0 |
is_stocked | Valgfri | Om produktet lagerføres |
Svarer 201 med det opprettede produktet.
GET /api/v1/invoices
Krever invoices:read. Sortert med nyeste fakturadato først.
| Parameter | Betydning |
|---|---|
contact_id | Bare fakturaer til denne kontakten |
unpaid_only | true gir bare ubetalte |
from, to | Avgrenser på fakturadato |
per_page | 1–100, standard 25 |
GET /api/v1/invoices/{id}
Krever invoices:read. Denne tar med fakturalinjene; listen gjør det ikke.
{
"data": {
"id": 42,
"invoice_number": "F-2026-0042",
"type": "invoice",
"contact_id": 12,
"invoice_date": "2026-06-15",
"due_date": "2026-07-15",
"amounts": {
"subtotal": 10000.00,
"discount_total": 0.00,
"vat_total": 2500.00,
"total": 12500.00,
"balance": 12500.00
},
"sent_at": "2026-06-15T10:02:00+02:00",
"paid_at": null,
"lines": [
{
"id": 91,
"description": "Konsulenttimer",
"quantity": 10.0,
"unit": "time",
"unit_price": 800.00,
"discount_percent": 0.0,
"vat_percent": 25.0,
"line_total": 8000.00,
"line_vat": 2000.00
}
],
"created_at": "2026-06-15T09:58:00+02:00"
}
}
balance er utestående beløp; null betyr betalt.
Fakturaer kan foreløpig bare leses. Å opprette en faktura berører nummerserie, MVA og hovedbok, og gjøres ikke fra et tynt API-kall før skrivestien er noe vi kan stå inne for.
Lister er paginert. Hvert svar har links og meta:
"meta": { "current_page": 2, "last_page": 6, "per_page": 25, "total": 143 }
Følg links.next til den er null. per_page kan settes opp til 100 — ber du om mer, får du 100.
For løpende synkronisering, bruk updated_since med tidspunktet for forrige vellykkede kjøring i stedet for å hente alt på nytt:
GET /api/v1/contacts?updated_since=2026-08-30T09:00:00%2B02:00&per_page=100
Et token får levetid når det opprettes: 3, 12 eller 24 måneder, eller uten utløp. Standard er tolv måneder.
Et token uten utløp er et token ingen husker å tilbakekalle når integrasjonen legges ned. Velg det bare når noe faktisk krever det, og noter hvorfor.
Et utløpt token slutter å virke av seg selv og svarer 401, akkurat som et tilbakekalt. Oversikten viser når hvert token utløper, og hvilke som står uten utløp.
Tokenet vises bare én gang. Bare en hash lagres, så det kan ikke hentes fram igjen — heller ikke av oss. Mister du det, tilbakekall det og lag et nytt.
Tilbakekalling virker umiddelbart. Integrasjoner som bruker tokenet slutter å virke ved neste kall, så lag heller ett token per integrasjon enn ett felles.
| Kode | Betyr |
|---|---|
| 401 | Tokenet mangler, er ukjent, tilbakekalt eller utløpt |
| 403 | Tokenet mangler tilgangen kallet krever |
| 404 | Ressursen finnes ikke i selskapet tokenet tilhører |
| 422 | Ugyldig innhold |
| 429 | For mange kall |
401 skiller ikke mellom ukjent, tilbakekalt og utløpt. Å si hvilket det er, hjelper en angriper med å kartlegge gyldige tokens.
403 sier hvilken tilgang som mangler:
{ "message": "Tokenet mangler tilgangen «invoices:read».", "required_scope": "invoices:read" }
422 gir feltvise meldinger:
{ "message": "...", "errors": { "name": ["Navn er påkrevd."] } }
120 kall per minutt per token. Kall uten token er begrenset hardere, siden avsenderen ikke er identifisert.
Grensen går per token, ikke per adresse: flere integrasjoner deler ofte én server, og en IP-basert grense ville latt en travel nabo strupe alle andre.
Ved 429 sier Retry-After hvor lenge du bør vente.
Opprett et endepunkt under Innstillinger → Integrasjoner og velg hvilke hendelser du vil høre om. URL-en må være https.
| Hendelse | Utløses når |
|---|---|
contact.created | En kontakt opprettes |
contact.updated | En kontakt endres |
invoice.created | En faktura opprettes |
invoice.sent | En faktura sendes |
invoice.paid | En faktura registreres betalt |
product.created | Et produkt opprettes |
voucher.received | Et bilag mottas |
Alle sju utløses. invoice.sent og invoice.paid kommer på overgangen — det øyeblikket feltet går fra tomt til satt — så du får dem én gang, ikke på nytt hver gang noe annet på fakturaen endres.
contact.updated utløses bare når noe en integrasjon bryr seg om er endret: navn, organisasjonsnummer, e-post, telefon, type, adresse, betalingsbetingelser eller aktiv-status. Nyttelasten sier hvilke felter det gjaldt, i changed. Interne oppdateringer som siste kontaktdato varsler ikke.
Hendelsene kommer fra selve datalaget, ikke fra enkelte skjermbilder, så de utløses uansett om posten kom fra grensesnittet, API-et, en import eller AI-assistenten.
Hvert kall er en POST med JSON:
{
"event": "invoice.sent",
"created_at": "2026-08-30T09:15:00+02:00",
"data": {
"id": 42,
"invoice_number": "F-2026-0042",
"contact_id": 12,
"total": 12500.00,
"due_date": "2026-07-15",
"sent_at": "2026-08-30T09:15:00+02:00"
}
}
Hvert kall bærer disse headerne:
| Header | Innhold |
|---|---|
X-Konrad-Signature | HMAC-SHA256 av tidsstempel.body |
X-Konrad-Timestamp | Unix-tid da kallet ble laget |
X-Konrad-Event | Hendelsen, for ruting |
X-Konrad-Delivery | Leverings-ID, for å kjenne igjen gjenforsøk |
$forventet = hash_hmac('sha256', $timestamp . '.' . $body, $hemmelighet);
if (! hash_equals($forventet, $signatur)) {
abort(400);
}
if (abs(time() - (int) $timestamp) > 300) {
abort(400);
}
Tidsstempelet er en del av det signerte, så et fanget kall kan ikke spilles av senere. Avvis alt eldre enn noen minutter, og sammenlign i konstant tid — en signaturkontroll som lekker timing er ingen kontroll.
Svarer du 2xx, regnes leveringen som fullført. Alt annet prøves på nytt med økende mellomrom: 10 sekunder, 1 minutt, 5 minutter, 15 minutter, 1 time.
Et endepunkt som feiler 15 ganger på rad slås av, og du ser det i oversikten. En URL som har vært død i to uker skal slutte å lage arbeid.
Leveringen skjer på kø, så et tregt endepunkt gjør ikke faktureringen treg.
Svar raskt. Ta imot kallet, legg det i din egen kø, og svar 200. Gjør du tungt arbeid før du svarer, får du gjenforsøk du ikke trenger.
Bruk X-Konrad-Delivery til å kjenne igjen gjenforsøk, så samme hendelse ikke behandles to ganger.
- Lag ett token per integrasjon, ikke ett felles — da kan du tilbakekalle én uten å stoppe resten
- Gi bare tilgangene som trengs. En nettbutikk som sender ordrer skal ikke kunne lese lønn
- Bruk
updated_sincei stedet for å hente alt på nytt - Tokens kan ikke hentes fram igjen. Mister du et, tilbakekall det og lag et nytt
- Et token som ikke har vært brukt vises med «Aldri brukt» — nyttig for å finne integrasjoner som aldri kom i gang