# Integrer Companybook i egen kode (REST API)

Denne guiden er skrevet for utviklere og kodeagenter som skal hente selskapsdata fra Companybook inn i et eget system, for eksempel et CRM, et datavarehus eller en kundeliste som skal berikes hver natt. Følg stegene i rekkefølge. Alt som står her kan kontrolleres mot [OpenAPI-kontrakten](https://companybook.co/api/v1/openapi.json).

## 1. Hva du trenger

- **En Companybook-konto med Plus.** REST API er en del av Plus (personlig) og Business Plus (team). Free-kontoer kan bruke MCP, men får `403 plan_required` på REST. Kontoer opprettes i lukket beta via [ventelisten](https://companybook.co/register).
- **En API-nøkkel.** Opprett den under [Agenttilgang](https://companybook.co/dashboard/mcp) → *Ny tilgang*. Er du medlem av et team med Business Plus, velg teamet under *Trekk credits fra*, så belastes teamets felles saldo.
- **Nøkkelen i en miljøvariabel på serveren**, for eksempel `COMPANYBOOK_API_KEY`. Aldri i kildekode, logger, nettleserkode eller en chat.

Kontroller tilgangen før du bygger noe:

```bash
curl -s https://companybook.co/api/v1/me -H "Authorization: Bearer $COMPANYBOOK_API_KEY"
```

Svaret skal ha `"api_access": true`. Feltet `credential` gjelder nøkkelen du kaller med: `billed_to` (`personal` eller `team`), hvilken saldo som belastes (`credential.credits`), grensene (`credential.limits`) og hvor mye som er brukt i dag og denne måneden (`credential.used`). For en teamnøkkel er det teamets saldo og grenser som gjelder; `credits` på toppnivå er brukerens personlige saldo. `/me` og `/capabilities` virker på alle planer; resten krever Plus.

## 2. Slik er API-et bygget opp

| Operasjon | Bruk |
|---|---|
| `GET /resolve?query=…&types=["org"]&limit=5` | Finn et selskap fra navn eller organisasjonsnummer. Gir kandidater med `id`, `name` og `score`. |
| `POST /entities/batch` | Hent opptil 100 selskaper i ett kall (25 på Free via MCP), med valgfrie utvidelser. **Dette er arbeidshesten for integrasjoner.** |
| `GET /entities/{id}` | Samme som over for ett selskap. |
| `POST /search` | Finn selskaper med filtre (kommune, NACE, omsetning, organisasjonsform). |
| `POST /traversals` | Følg relasjoner som eierskap, maks tre hopp. |
| `GET /usage` | Eget forbruk siste 30 dager. |

Base-URL er `https://companybook.co/api/v1`. Alle svar på dataoperasjoner er en `cb/v1`-konvolutt med `data`, `coverage`, `warnings`, `as_of`, `truncated` og `cost`.

Selskaps-ID-er har formen `cb:org:<organisasjonsnummer>`, for eksempel `cb:org:923609016`. Et organisasjonsnummer er alltid 9 siffer.

## 3. Hent mange selskaper i ett kall

Har du organisasjonsnumrene, bruk `POST /entities/batch` med `extend`:

```bash
curl -s https://companybook.co/api/v1/entities/batch \
  -H "Authorization: Bearer $COMPANYBOOK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ids": ["cb:org:923609016", "cb:org:817209882"],
    "extend": ["financial_history", "subunits", "web_identities", "group_parent"]
  }'
```

- `ids`: 1–100 ID-er per kall. Anbefalt: **50 per kall når du bruker alle fire utvidelsene**, og kall sekvensielt, ikke parallelt.
- `extend` er valgfritt. Utelat utvidelser du ikke trenger; hver utvidelse øker datamengden som måles.
- `include` er noe annet: relasjoner (kanter) følger bare med når du ber om dem, for eksempel `["HAS_ROLE"]`, eller `["*"]` for alle. Uten `include` får du ingen kanter, men hvert selskap har `active_predicates` som viser hvilke relasjoner som finnes og hvor mange. Kanter teller som poster.

### Felter per selskap (`data.entities[].payload`)

| Felt | Betydning |
|---|---|
| `org_nr`, `name` | Organisasjonsnummer og navn |
| `entity_type`, `parent_org_nr` | `hovedenhet` eller `underenhet`, og hovedenheten til en underenhet |
| `org_form`, `org_form_description` | Organisasjonsform, f.eks. `AS` |
| `nace_code`, `nace_description`, `secondary_nace_code`, `secondary_nace_description` | Næringskoder |
| `address`, `postal_code`, `city`, `municipality`, `municipality_code` | Forretningsadresse |
| `postal_address`, `postal_address_postal_code`, `postal_address_city` | Postadresse |
| `latitude`, `longitude` | Koordinater for forretningsadressen |
| `employees`, `founded_date`, `registration_date` | Ansatte og datoer |
| `is_bankrupt`, `is_liquidating`, `is_under_forced_liquidation`, `is_deleted`, `deleted_at`, `cessation_date` | Status. Bruk disse til kredittvarsel |
| `is_in_group`, `last_annual_report_year`, `registered_in_vat` | Konsernflagg, siste regnskapsår, MVA-registrering |

Et felt som mangler i `payload`, er ukjent. Det betyr ikke `false` eller `0`.

### Utvidelser (`data.extensions`, nøklet på organisasjonsnummer)

| Utvidelse | Innhold |
|---|---|
| `financial_history` | Alle regnskapsår, nyeste først. Kun selskapsregnskap (ikke konsern). Ett år per rad; er året både fra Proff og Brønnøysund, vinner Proff. Felter: `fiscal_year`, `revenue`, `operating_costs`, `operating_result`, `result_before_tax`, `net_result`, `total_assets`, `total_equity`, `total_debt`, `source`. Maks 30 år. |
| `subunits` | Underenheter registrert på hovedenheten, med adresse, næring, ansatte og koordinater. Maks 50 per selskap i en batch. Ber du om ett selskap alene (`GET /entities/{id}`), får du opptil 1000. Er en liste avkortet, står organisasjonsnummeret i `data.extensions_truncated.subunits`. Underenhetene har de samme feltnavnene som selskapet (`nace_code`, `nace_description`, `address`, `postal_code`, `city`, `municipality_code`). De gamle registernavnene (`industry_code`, `address_street` osv.) følger med til v2, men ny kode bør bruke de nye. |
| `web_identities` | Nettsted og sosiale profiler: `identity_type`, `full_url`, `is_primary`, og for nettsteder `site_kind` og `site_status` (skill et aktivt nettsted fra et parkert domene). |
| `group_parent` | Øverste eier i eierkjeden (`org_nr`, `name`, `depth`), fulgt gjennom aksjonærregisteret, eller `null` hvis ingen eier over selskapet er registrert. **Ikke det samme som registrert konsern:** `group_parent` finnes også for selskaper som ikke er i et konsern i Brønnøysund (`is_in_group` er `false`). Vil du bare ha konsernmor i registerets forstand, bruk `group_parent` kun når `is_in_group` er `true`. |

Kontaktinformasjon som e-post og telefon leveres ikke via API-et.

Utvidelsene gjelder selskapet du ber om. For en underenhet ligger regnskap, konsern og ofte nettsted på hovedenheten: hent `parent_org_nr` i samme eller neste kall.

## 4. SDK for TypeScript og JavaScript (beta)

`@companybook/sdk` pakker stegene over: nøkkelkontroll, batcher på 50, sekvensielle kall, `Retry-After` ved `429` og typede feil. Ingen avhengigheter; krever Node 20 eller nyere.

```ts
import { Companybook } from '@companybook/sdk';

const cb = new Companybook({ apiKey: process.env.COMPANYBOOK_API_KEY });
const { entities, extensions, missing, units } = await cb.entities.batchAll(orgNumbers, {
  extend: ['financial_history', 'web_identities', 'group_parent'],
});
const { subunits, truncated } = await cb.entities.subunits('817209882');
const left = await cb.remaining(); // poster og kall igjen i dag og denne måneden
```

Feil kommer som `CompanybookError` med `status`, `code`, `retryAfter` og `fatal` (`401`, `402`, `403`: stopp kjøringen).

## 5. Når du bare har et navn

Bruk `GET /resolve` og kontroller treffet før du lagrer koblingen:

```bash
curl -s -G https://companybook.co/api/v1/resolve \
  -H "Authorization: Bearer $COMPANYBOOK_API_KEY" \
  --data-urlencode 'query=Vinmonopolet' \
  --data-urlencode 'types=["org"]' \
  --data-urlencode 'limit=5'
```

`score` rangerer kandidatene, men er ikke en sannsynlighet. Godta et treff automatisk bare når navnet og postnummeret eller poststedet stemmer med det du har fra før. Ellers: legg det til manuell kontroll.

## 6. Forbruk og grenser

- **All bruk måles, også gratis data.** Grunndata koster 0 credits, men teller mot volumgrensene: på Plus 120 kall i minuttet, 10 000 kall og 50 000 poster per døgn. Utvidelser teller som poster.
- API-et **reserverer** plass før kallet og avregner faktisk mengde etterpå. Et kall med 100 ID-er og alle utvidelser reserverer mye, men belaster bare det som leveres.
- Sjekk `credential.used` og `credential.limits` i `GET /me` før en stor kjøring; tellerne er de samme som grensene håndheves mot, og `credential.resets` sier når døgnet og måneden nullstilles (UTC).
- Følg eget forbruk i [bruksoversikten](https://companybook.co/dashboard/mcp/usage) eller med `GET /usage`. Teamnøkler vises under teamet i bruksoversikten.

## 7. Feil og hva du skal gjøre

| Status og kode | Betydning | Gjør dette |
|---|---|---|
| `401 authentication_required` / `invalid_token` | Nøkkelen mangler, er feil eller er tilbakekalt | Stopp. Ikke prøv igjen med samme nøkkel. |
| `403 plan_required` | Planen dekker ikke operasjonen; svaret har `required_tier` og `upgrade_url` | Stopp og si fra til eieren av kontoen. |
| `400 invalid_arguments` | For mange ID-er, ukjent utvidelse eller feil format | Rett forespørselen; ikke prøv igjen uendret. |
| `402 credits_exhausted` | Saldoen er brukt opp | Stopp til neste måned eller til saldoen er økt. |
| `429 volume_limit` / `expense_limit` | Volumgrensen er nådd | Vent minst `Retry-After` sekunder. Ikke lag flere nøkler for å komme rundt grensen. |
| `503 data_unavailable` / `metering_unavailable` | Midlertidig feil hos oss | Prøv igjen senere med samme forespørsel. |

## 8. Anbefalt oppsett for en nattlig synk

1. Les listen over organisasjonsnumre fra ditt eget system og fjern duplikater.
2. Del opp i grupper på 50 og kall `POST /entities/batch` sekvensielt med de utvidelsene du trenger.
3. Lagre `as_of` sammen med dataene. Hent ikke samme selskap oftere enn én gang i døgnet: registerdata endrer seg sjelden raskere.
4. Ved `429`: vent `Retry-After` og fortsett der du slapp. Ved `403` eller `401`: avbryt hele kjøringen og varsle.
5. Logg antall selskaper, antall mangler (`coverage.missing`) og `cost.units` per kjøring. Logg aldri nøkkelen.

## 9. Slik vet du at integrasjonen virker

- `GET /me` gir `api_access: true`.
- Et batch-kall med `cb:org:923609016` (Equinor ASA) gir `entity_type: "hovedenhet"` og flere år i `data.extensions.financial_history["923609016"]`.
- Et kall med en ID som ikke finnes gir ID-en i `coverage.missing`, ikke en feil.
- Forbruket vises i bruksoversikten innen et minutt.

## Mer

- [OpenAPI-kontrakt](https://companybook.co/api/v1/openapi.json) — autoritativ beskrivelse av alle operasjoner.
- [MCP-guide](https://companybook.co/no/guide/mcp) — for agenter som kobler seg til med MCP i stedet for REST.
- [Plan og tilgang](https://companybook.co/dashboard/plan) — hva som inngår i Free og Plus.
