API

Alt som kan gjøres i panelet kan gjøres via API-et. Oppslagssiden i panelet bruker de samme endepunktene. API-nøkler lages under API i panelet og krever Senter-pakken eller større.

Autentisering

Send nøkkelen som Bearer-token. Kall går mot virksomhetens egen adresse, eller mot hvilken som helst adresse under declarely.app; nøkkelen avgjør hvilken virksomhet det gjelder.

curl "https://<slug>.declarely.app/api/v1/lookup?phone=%2B4791234567" \
  -H "Authorization: Bearer dk_live_…"

Nøkler har rettighetene read og/eller write. Alle svar er JSON. Feil returnerer { error, message, details? } med passende HTTP-status.

Endepunkter

GET/api/v1/lookup?phone=+4791234567scope: read

Sjekk om et telefonnummer har en gyldig erklæring. Nummeret kan skrives med eller uten landskode; norske numre er standard.

Svar

{
  "phone": "+4791234567",
  "found": true,
  "declaration": {
    "id": "…", "fullName": "Kari Nordmann", "firstName": "Kari",
    "birthDate": "1988-03-14", "phone": "+4791234567", "email": "kari@example.com",
    "signedAt": "2026-09-05T10:12:00Z", "expiresAt": "2027-09-05T10:12:00Z",
    "locale": "nb", "source": "kiosk",
    "children": [{ "id": "…", "fullName": "Ola Nordmann", "birthDate": "2016-05-01", "signedAt": "…" }]
  }
}
GET/api/v1/declarations?query=&page=1&page_size=25&include_expired=false&since=scope: read

Liste over erklæringer, nyeste først. query matcher telefon, navn eller e-post. since er et ISO-tidspunkt.

Svar

{ "items": [ … ], "total": 128, "page": 1, "pageSize": 25 }
GET/api/v1/declarations/{id}scope: read

Én erklæring med barn, IP-adresse og nettleser.

POST/api/v1/declarationsscope: write

Registrer en erklæring på vegne av en gjest. Samme felter som skjemaet. termsVersionId må være gjeldende publiserte versjon (se /terms). accepted må være true.

Body

{
  "phone": "912 34 567",
  "birthDate": "1988-03-14",
  "fullName": "Kari Nordmann",
  "email": "kari@example.com",
  "locale": "nb",
  "termsVersionId": "…",
  "accepted": true,
  "children": [{ "fullName": "Ola Nordmann", "birthDate": "2016-05-01" }],
  "childrenConfirmed": true
}

Svar

201 { "declaration": { … } }
DELETE/api/v1/declarations/{id}scope: write

Trekk tilbake en erklæring. Raden beholdes til utløp for sporbarhet, men regnes som ugyldig.

GET/api/v1/terms?locale=nbscope: read

Gjeldende publiserte vilkår i ønsket språk, med versjons-ID til bruk ved registrering.

GET/api/v1/tenantscope: read

Innstillinger, pakke, abonnementsstatus og bruk.

GET/api/v1/statsscope: read

Tall for dashbord: aktive, signert siste 7 og 30 dager, utløper neste 30 dager, barn, kilder.

POST/api/v1/passes/scanscope: read

Krever kortmodulen. Løs inn QR-koden fra gjestens telefon: { token }. Svaret gir korthavere, kort med saldo og gyldige egenerklæringer i ett kall. Endrer ingenting - trekk skjer med /passes/use.

GET/api/v1/passes/lookup?phone=%2B4791234567scope: read

Samme svar som skanning, men fra telefonnummer. Reserveløsningen når QR-koden ikke lar seg lese.

GET/api/v1/passes/card?uid=04A23B5Cscope: read

Slå opp et fysisk kort - brikken, armbåndet eller plastkortet gjesten legger på NFC/RFID-leseren. Samme svar som skanning, med scannedPassId som peker på kortet brikken hører til. Skilletegn og små bokstaver spiller ingen rolle; 404 card_unknown betyr at brikken ikke er registrert hos dere.

POST/api/v1/passes/usescope: write

Trekk gavekortsaldo eller klipp, eller registrer årskortbesøk. Års- og klippekort bruker { passId, quantity, idempotencyKey }. Gavekort bruker { passId, amountMinor, externalRef?, idempotencyKey }. Beløpet er i øre. Samme idempotencyKey to ganger trekker bare én gang.

POST/api/v1/passes/reversescope: write

Angre et trekk eller et besøk: { eventId }. Klippene kommer tilbake, og angringen blir stående i historikken. Svarer som /use med 200 og ok:false dersom hendelsen allerede er angret (already_reversed) eller ikke kan angres (not_reversible).

GET/api/v1/passes?query=&kind=&page=1&page_size=25scope: read

Kort for virksomheten, nyeste først. query treffer navn, eget eller foresatts telefonnummer, kortnummer eller fysisk brikkenummer. Treffer på en voksen tar også med barnas kort. Sidestørrelsen er 25 som standard og maks 100, så en full eksport må bla med page.

Feilkoder

invalid_phoneTelefonnummeret kunne ikke tolkes.
too_youngGjesten er under minstealderen. details.minAge.
email_requiredVirksomheten krever e-post.
child_too_oldEt barn er 18 år eller eldre og må signere selv.
too_many_childrenFlere barn enn tillatt. details.max.
children_confirm_requiredchildrenConfirmed må være true når barn er med.
terms_outdatedtermsVersionId er ikke gjeldende versjon. Hent /terms på nytt.
no_termsVirksomheten har ikke publisert vilkår.
plan_limitPakkens grense for aktive erklæringer er nådd.
card_unknownBrikken er ikke registrert på noe kort hos dere.
invalid_card_uiduid kunne ikke leses som et kortnummer.
rate_limitedFor mange kall. Vent og prøv igjen.
unauthorizedManglende eller ugyldig API-nøkkel.
forbiddenNøkkelen mangler rettigheten, eller pakken inkluderer ikke API.
plan_no_apiPakken inkluderer ikke API-tilgang.
module_offKortmodulen er ikke slått på for virksomheten.
invalid_tokenQR-koden er utløpt eller er ikke et Declarely-kort. Be gjesten hente den fram på nytt.
wrong_tenantQR-koden hører til en annen virksomhet.

Avslag på kortbruk

/api/v1/passes/use og /reverse svarer 200 med { ok: false, reason } når kortet finnes, men handlingen ikke kan utføres. Et ugyldig quantity er derimot en vanlig 400 med error validation. Det er ikke en feil i kallet, og forsøket blir stående i historikken på kortet, slik at kunden kan se at noen prøvde. Vis grunnen til operatøren.

blockedKortet er sperret.
refundedKortet er refundert.
expiredKortet er utløpt.
not_startedKortet gjelder fra en senere dato.
insufficientFærre klipp igjen enn det som ble forsøkt trukket.
wrong_typeHandlingen passer ikke korttypen.
already_reversedHendelsen er allerede angret.
not_reversibleHendelsen kan ikke angres.

Innbygging

Skjemaet kan bygges inn på egen nettside. Rammen tilpasser høyden automatisk.

<iframe src="https://<slug>.declarely.app/embed" class="declarely-embed"
        style="width:100%;border:0;min-height:640px" title="Egenerklæring"></iframe>
<script src="https://<slug>.declarely.app/embed.js" async></script>