Kossorna

v1 · för utvecklare

Kossorna CDB-API

Ger journalsystem tillgång till nötkreatursdata ur Jordbruksverkets centrala nötkreatursregister (CDB), samt hanterar de medlemmar och samtycken som ger rätten till datan.

Den här sidan förklarar upplägget. Det fullständiga kontraktet - fältnamn, scheman, exakta felkuvert - står i OpenAPI-specen. Vid konflikt är det den filen som gäller.

Kom igång

Stegen låser upp varandra i tur och ordning: utan BusinessUuid går inga medlemsanrop, och utan signerat samtycke svarar djurdatan 401 hur giltig din nyckel än är.

  1. 1

    Teckna avtal med Jordbruksverket

    Ett avtal om utlämnande av CDB-uppgifter mellan er och Jordbruksverket är en förutsättning för att få en API-nyckel. Utan det avtalet får uppgifterna inte lämnas ut till er, och ingen nyckel skapas. Avtalets bilaga 1 avgör dessutom VILKA uppgifter ni får se - den styr vilka fält som kommer med i svaren.

  2. 2

    Få en API-nyckel av kossorna

    När avtalet är på plats skapar kossorna nyckeln. Den hör till journalsystemet, inte till en enskild verksamhet. Du får den tillsammans med basadressen.

  3. 3

    Onboarda verksamheten

    Svaret innehåller ett BusinessUuid. Spara det - det ska med som header i allt som rör medlemmar och djur. Anropet är en upsert, så det går att köra om.

    POST /api/v1/businesses

  4. 4

    Lägg till djurhållaren

    Har du redan ett signerat samtycke skickar du med det i anropet. Har du inte det skickar kossorna automatiskt ett SMS till djurhållaren med en signeringslänk. Anropet är idempotent på personnummer - kör det så ofta du vill.

    POST /api/v1/members

  5. 5

    Vänta in samtycket

    Innan djurhållaren signerat svarar djurdatan 401 med code no_consent. Det är det enda 401 som löser sig av sig självt. Kossorna pingar din webhook när något ändrats.

    GET /api/v1/members/{ssn}/find_by_ssn

  6. 6

    Hämta anläggningarna

    Lätt anrop som ger djurhållarens anläggningar med antal levande djur på varje. Använd det för att bygga en lista att välja i.

    GET /api/v1/cdb/{ssn}/ppns_for_ssn

  7. 7

    Hämta djuren

    Samma svar som steg 5 fast med hela djurlistan per anläggning. Tyngre - hämta det när användaren valt anläggning, inte i förväg.

    GET /api/v1/cdb/{ssn}/ppns_with_animals_for_ssn

  8. 8

    Håll dig uppdaterad

    Webhookpingen betyder 'hämta om den här medlemmen', inte vad som hänt. Vad som faktiskt förändrats i beståndet läser du ur registerhållnings-endpointsen. Om datan alls är färsk svarar last_file_date på - ett tomt svar från ett dygn då leveransen uteblev är inte samma sak som att inget hänt.

    GET /api/v1/cdb/{ssn}/dead · GET /api/v1/cdb/{ssn}/departed · GET /api/v1/last_file_date

Så hänger det ihop

Fem nivåer. Varje anrop du gör pekar ut var i kedjan du befinner dig.

  1. kund - journalsystemet självt, det som håller API-nyckeln
  2. verksamhet (business) - veterinärpraktiken eller motsvarande, identifieras av BusinessUuid
  3. medlem - djurhållaren, identifieras av person- eller organisationsnummer
  4. anläggning - platsen där djuren står
  5. djur - det enskilda nötkreaturet

Ord som betyder samma sak, eller nästan

aktuellt_ppn
Anläggningen djuret står på just nu.
anläggning
Anläggning, produktionsplats och PPN är tre ord för samma sak. Fältet heter `plants` i API:t och `produktionsplats` hos Jordbruksverket.
djurets uuid
Djurets stabila nyckel. Den ändras aldrig under djurets liv, till skillnad från öronnumret. Lagra uuid, inte öronnumret - se fallgroparna.
öronnummer / identitet
Djurets läsbara namn, t.ex. SE-900001-0222-2. Kan bytas under djurets liv; ett djur kan ha haft flera.

Autentisering

Token identifierar kunden, BusinessUuid verksamheten, och samtycket avgör vilken medlems data som får läsas. Alla tre måste stämma - en giltig token räcker inte.

Authorization
Kundens API-nyckel. `Bearer <nyckel>` eller bara `<nyckel>` - ledande `Bearer ` strippas serversidan. Identifierar journalsystemet. Nyckeln förutsätter ett avtal om utlämnande av CDB-uppgifter med Jordbruksverket.
BusinessUuid
Vilken verksamhet anropet gäller. Värdet är det uuid som POST /businesses gav. Krävs av alla /members- och /cdb-anrop utöver Authorization. Läses skiftlägesokänsligt.

Bara den här menyn och /healthz. Allt annat svarar 401.

Samtycke

Ingen djurdata utan att djurhållaren gett verksamheten samtycke. Saknas det svarar /cdb 401 med code `no_consent`.

BankID-signering (bankid_order_ref)

FrejaID-signering (freja_sign_ref)

Uppladdat signerat dokument (fil)

Om inget samtycke skickas med
Skapas en medlem utan något av dem skickar kossorna automatiskt ett SMS till medlemmen med en signeringslänk. Går numret inte att skicka till svarar POST /members 422 - men medlemmen är ändå skapad.
Vem som får signera
Är medlemmen ett företag får vem som helst signera. Är medlemmen privatperson eller enskild firma måste signaturen bära medlemmens eget personnummer - annars sparas ingenting.
Innan det finns
`no_consent` kan lösa sig av sig självt när medlemmen signerar. Det är det enda 401 som är värt att fråga igen på senare.

Var datan kommer ifrån

Ursprung
All djur- och anläggningsdata kommer från Jordbruksverkets centrala nötkreatursregister. Kossorna beställer, hämtar och tolkar filerna; API:t serverar resultatet.
Takt
Beställning skickas kvällen före, filerna hämtas och tolkas morgonen därpå. Datan är alltså upp till ett dygn gammal. Det finns inget sätt att trigga en hämtning via API:t.
Två leveranser per dygn
Varje dygn beställs två filuppsättningar. INKREMENTELL bär förändringarna för de djurhållare som redan fått en fullständig leverans; TOTAL bär hela beståndet för dem som aldrig fått en, plus de äldsta i rotationen. Uppsättningarna överlappar inte.
Hur färsk är datan?
GET /api/v1/last_file_date svarar när filerna senast blev korrekt inlästa. Datumet är BESTÄLLNINGENS dag, alltså vilket läge datan speglar - inte när vi hämtade den. Svaret delas upp i `total` och `incremental` därför att de två leveranserna täcker olika djurhållare: den inkrementella går till i stort sett alla varje dygn, den totala till en roterande delmängd. Ett anrop per dygn räcker gott.
Första leveransen
En nytillagd medlems djurdata finns inte förrän hennes första fullständiga leverans har hämtats - normalt morgonen efter att samtycket kom in. Tomma listor dessförinnan betyder 'ännu inte levererat', inte 'inga djur'.
Eftersläpning
Jordbruksverket kan leverera en händelse långt i efterhand. Därför räknar /dead och /departed sina fönster på när VI bokförde händelsen, inte på när den inträffade.

Endpoints

Listan genereras ur applikationens router, så den kan inte bli inaktuell. Kolumnen Kräver visar vilka headers anropet behöver.

Menyn

GET

/api/v1

Den här menyn. Öppen, kräver ingen token.

öppen

Verksamheter

GET

/api/v1/businesses

Verksamheterna som den här kundens token är kopplad till.

Authorization
POST

/api/v1/businesses

Onboarda en verksamhet och få tillbaka BusinessUuid, som alla medlems- och djuranrop kräver. Upsert på verksamhetens orgnr per kund: 201 nytt, 200 ny koppling, 409 om kopplingen redan finns.

Authorization
GET

/api/v1/businesses/:id

En verksamhet. :id är BusinessUuid, inte ett löpnummer.

Authorization
PATCH

/api/v1/businesses/:id

Uppdatera verksamhetens uppgifter.

Authorization
PUT

/api/v1/businesses/:id

Samma som PATCH.

Authorization

Medlemmar och samtycken

GET

/api/v1/members

Verksamhetens medlemmar.

Authorization BusinessUuid
POST

/api/v1/members

Lägg till en djurhållare under verksamheten. Idempotent på personnummer inom verksamheten: finns medlemmen uppdateras den (200) i stället för att dubbletteras (201). Samtycke kan följa med i anropet (bankid_order_ref, freja_sign_ref eller uppladdad fil); utan det skickas ett SMS till medlemmen med en signeringslänk.

Authorization BusinessUuid
DELETE

/api/v1/members/:id

Genererad av `resources` men MemberController saknar delete/2. Anropet ger 500. Ett samtycke återkallas, medlemmen raderas inte.

Ej implementerad - anropet svarar 500. Använd anropet som beskrivs ovan i stället.

Authorization BusinessUuid
GET

/api/v1/members/:id

En medlem, med samtyckesstatus och SMS-status.

Authorization BusinessUuid
PATCH

/api/v1/members/:id

Genererad av `resources` men MemberController saknar update/2. Anropet ger 500. POST /api/v1/members uppdaterar i stället, på personnummer.

Ej implementerad - anropet svarar 500. Använd anropet som beskrivs ovan i stället.

Authorization BusinessUuid
PUT

/api/v1/members/:id

Samma som PATCH - saknar update/2, ger 500.

Ej implementerad - anropet svarar 500. Använd anropet som beskrivs ovan i stället.

Authorization BusinessUuid
GET

/api/v1/members/:id/edit

Genererad av `resources` men MemberController saknar edit/2. Anropet ger 500.

Ej implementerad - anropet svarar 500. Använd anropet som beskrivs ovan i stället.

Authorization BusinessUuid
GET

/api/v1/members/:ssn/find_by_ssn

Slå upp en medlem på personnummer i stället för id. Det här är vägen ett journalsystem normalt går - det har personnumret, inte vårt id.

Authorization BusinessUuid
GET

/api/v1/members/new

Genererad av `resources` men MemberController saknar new/2. Anropet ger 500. Använd POST /api/v1/members.

Ej implementerad - anropet svarar 500. Använd anropet som beskrivs ovan i stället.

Authorization BusinessUuid

Anläggningar

GET

/api/v1/plants

Alla anläggningar kunden har rätt att se.

Authorization
GET

/api/v1/plants/:id

En anläggning.

Authorization
GET

/api/v1/plants/:ssn/by_ssn

Anläggningar för en djurhållare, uppslaget på hennes person- eller organisationsnummer.

Authorization

Djurdata

GET

/api/v1/cdb/:ssn/animals/:id

Ett enskilt djur. :id är djurets uuid - se fallgropen om uuid mot öronnummer.

Authorization BusinessUuid
GET

/api/v1/cdb/:ssn/dead

Djur som dött, räknat på när VI bokförde dödsfallet - inte på dödsdatumet. from_days/to_days räknas bakåt från idag, inklusiva i båda ändar, max 365 dagar bakåt.

Authorization BusinessUuid
GET

/api/v1/cdb/:ssn/departed

Djur som lämnat anläggningen, med samma fönsterlogik som dead.

Authorization BusinessUuid
GET

/api/v1/cdb/:ssn/ppns_for_ssn

Medlemmens anläggningar med antal levande djur på var och en. Lätt anrop - använd det för att bygga en lista att välja i, innan du hämtar djuren.

Authorization BusinessUuid
GET

/api/v1/cdb/:ssn/ppns_with_animals_for_ssn

Samma som ppns_for_ssn, fast med hela djurlistan per anläggning. Tyngre anrop.

Authorization BusinessUuid

Drift och datafärskhet

GET

/api/v1/last_file_date

När Jordbruksverkets filer senast blev korrekt inlästa - hämtade OCH tolkade. Redovisas per beställningstyp, eftersom TOTAL och INKREMENTELL täcker olika djurhållare. Använd den för att skilja 'inget har hänt' från 'dagens leverans uteblev'. Kräver bara Authorization; svaret är detsamma för alla verksamheter.

Authorization

När något går fel

Gren på `code` i kroppen, inte på statuskoden. 401 är överlagrat och betyder både 'ogiltig token' och 'du saknar rätt till den här medlemmen'.

invalid_token Authorization saknas eller matchar ingen kund.
missing_business BusinessUuid saknas eller är okänd.
no_customer_business_link Din kund är inte kopplad till den verksamheten.
no_business_member_link Verksamheten har ingen koppling till den medlemmen.
no_consent Kopplingen finns men medlemmen har inget aktivt samtycke.
invalid_ssn Personnumret går inte att tolka.
member_not_found Ingen medlem med det numret.
animal_not_found Inget djur med det uuid:t hos den medlemmen.

4xx ska inte retryas - samma anrop ger samma svar. 5xx och nätverkstimeout är den enda rimligt retrybara klassen. Ingen Retry-After skickas.

Manuella fel ger {"error": "..."}, valideringsfel ger {"errors": {"fält": [...]}}, oväntade fel ger {"errors": {"detail": "..."}}. `code` finns på auth-felen och på /cdb, men inte överallt. Läs OpenAPI-filen innan du parsar felkroppar hårt.

Webhook

Kossorna pingar kundens konfigurerade webhook_url när en medlems uppgifter kan ha ändrats. Pingen är en GET utan body - den säger 'hämta om den här medlemmen', inte vad som hänt.

GET {webhook_url}/{VERKSAMHETENS_SSN}/business/{MEDLEMMENS_SSN}/member

Triggas av
medlem skapad eller uppdaterad · samtycke godkänt eller signerat · SMS-status från operatören
Omförsök
Första anropet plus tre omförsök: efter 10 minuter, 2 timmar och 24 timmar.
Ingen signatur
Det finns ingen HMAC eller signatur idag. Behandla pingen som ett tips om att hämta, aldrig som en betrodd uppgift i sig.

Fallgropar

Det här är sådant som inte syns i en fältlista, men som kostar tid att upptäcka själv.

Lagra djurets uuid, inte öronnumret

Öronnumret kan bytas när djuret märks om. Ett system som nycklar på SE-900001-0222-2 tappar djuret vid ommärkning. uuid ändras aldrig.

En anläggning har två adresser - blanda inte ihop dem

`adress`/`postnummer`/`postadress` är platsens egen. `djurhallare_*` är djurhållarens. En djurhållare med flera anläggningar har EN djurhållaradress men olika platsadresser - visar du djurhållarens blir hennes anläggningar omöjliga att skilja åt i listan.

Person- och organisationsnummer förekommer i både tio- och tolvsiffrig form

Samma djurhållare kan ligga lagrad i båda formaten. Jämför inte råa strängar rakt av; matcha på de sista tio siffrorna när du parar ihop en djurhållare mellan system.

En tom djurlista betyder inte alltid noll djur

Innan medlemmens första fullständiga leverans hämtats finns ingen data att visa. Skilj 'ännu inte levererat' från 'inga djur' innan du meddelar en användare något. `member.sjv_status` säger om orsaken är känd: `orgnr_invalid` betyder att numret måste rättas hos dig, `orgnr_not_found` att SJV inte känner igen det ännu - det senare får aldrig visas som ett skrivfel, det är också svaret för skyddad identitet. GET /api/v1/last_file_date säger om leveransen över huvud taget kommit in idag.

Döda och avförda djur ligger kvar en tid

Ett djur som dött eller lämnat anläggningen ligger kvar i svaret i 40 dagar, så att anslutande system hinner se ATT det hände i stället för att djuret tyst försvinner mellan två anrop.

avliden_datum kan ligga långt bak i tiden på ett djur du får idag

Fönstren i /dead och /departed räknas på när vi bokförde händelsen. Räknar du själv på avliden_datum missar du djur som levererades sent.

Vilka fält du får se styrs av kundavtalet

Anläggningsfält är flaggstyrda per kund och speglar bilaga 1 i kundens SJV-avtal. Ett fält som saknas i ditt svar behöver inte saknas i datan - din kund kan sakna rätten.

POST /members är en upsert, inte ett create

Samma personnummer två gånger ger en uppdatering (200), inte en dubblett (201). Kör den så ofta du vill.

Basadress i produktion: https://kossorna.se · lokal utveckling: http://localhost:4000

Allt på den här sidan finns också som JSON på /api/v1, för den som hellre läser maskinellt.