{"version":"v1","autentisering":{"headers":{"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."},"ordning":"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.","utan_token":"Bara den här menyn och /healthz. Allt annat svarar 401."},"datakalla":{"efterslapning":"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.","forsta_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'.","hur_farsk_ar_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.","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.","tva_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.","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."},"endpoints":[{"status":"live","beskrivning":"Den här menyn. Öppen, kräver ingen token.","kraver":[],"metod":"GET","omrade":"meny","sokvag":"/api/v1"},{"status":"live","beskrivning":"Verksamheterna som den här kundens token är kopplad till.","kraver":["Authorization"],"metod":"GET","omrade":"verksamheter","sokvag":"/api/v1/businesses"},{"status":"live","beskrivning":"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.","kraver":["Authorization"],"metod":"POST","omrade":"verksamheter","sokvag":"/api/v1/businesses"},{"status":"live","beskrivning":"En verksamhet. :id är BusinessUuid, inte ett löpnummer.","kraver":["Authorization"],"metod":"GET","omrade":"verksamheter","sokvag":"/api/v1/businesses/:id"},{"status":"live","beskrivning":"Uppdatera verksamhetens uppgifter.","kraver":["Authorization"],"metod":"PATCH","omrade":"verksamheter","sokvag":"/api/v1/businesses/:id"},{"status":"live","beskrivning":"Samma som PATCH.","kraver":["Authorization"],"metod":"PUT","omrade":"verksamheter","sokvag":"/api/v1/businesses/:id"},{"status":"live","beskrivning":"Verksamhetens medlemmar.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"medlemmar","sokvag":"/api/v1/members"},{"status":"live","beskrivning":"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.","kraver":["Authorization","BusinessUuid"],"metod":"POST","omrade":"medlemmar","sokvag":"/api/v1/members"},{"status":"ej_implementerad","beskrivning":"Genererad av `resources` men MemberController saknar delete/2. Anropet ger 500. Ett samtycke återkallas, medlemmen raderas inte.","kraver":["Authorization","BusinessUuid"],"metod":"DELETE","omrade":"medlemmar","sokvag":"/api/v1/members/:id"},{"status":"live","beskrivning":"En medlem, med samtyckesstatus och SMS-status.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"medlemmar","sokvag":"/api/v1/members/:id"},{"status":"ej_implementerad","beskrivning":"Genererad av `resources` men MemberController saknar update/2. Anropet ger 500. POST /api/v1/members uppdaterar i stället, på personnummer.","kraver":["Authorization","BusinessUuid"],"metod":"PATCH","omrade":"medlemmar","sokvag":"/api/v1/members/:id"},{"status":"ej_implementerad","beskrivning":"Samma som PATCH - saknar update/2, ger 500.","kraver":["Authorization","BusinessUuid"],"metod":"PUT","omrade":"medlemmar","sokvag":"/api/v1/members/:id"},{"status":"ej_implementerad","beskrivning":"Genererad av `resources` men MemberController saknar edit/2. Anropet ger 500.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"medlemmar","sokvag":"/api/v1/members/:id/edit"},{"status":"live","beskrivning":"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.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"medlemmar","sokvag":"/api/v1/members/:ssn/find_by_ssn"},{"status":"ej_implementerad","beskrivning":"Genererad av `resources` men MemberController saknar new/2. Anropet ger 500. Använd POST /api/v1/members.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"medlemmar","sokvag":"/api/v1/members/new"},{"status":"live","beskrivning":"Alla anläggningar kunden har rätt att se.","kraver":["Authorization"],"metod":"GET","omrade":"anlaggningar","sokvag":"/api/v1/plants"},{"status":"live","beskrivning":"En anläggning.","kraver":["Authorization"],"metod":"GET","omrade":"anlaggningar","sokvag":"/api/v1/plants/:id"},{"status":"live","beskrivning":"Anläggningar för en djurhållare, uppslaget på hennes person- eller organisationsnummer.","kraver":["Authorization"],"metod":"GET","omrade":"anlaggningar","sokvag":"/api/v1/plants/:ssn/by_ssn"},{"status":"live","beskrivning":"Ett enskilt djur. :id är djurets uuid - se fallgropen om uuid mot öronnummer.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"djurdata","sokvag":"/api/v1/cdb/:ssn/animals/:id"},{"status":"live","beskrivning":"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.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"djurdata","sokvag":"/api/v1/cdb/:ssn/dead"},{"status":"live","beskrivning":"Djur som lämnat anläggningen, med samma fönsterlogik som dead.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"djurdata","sokvag":"/api/v1/cdb/:ssn/departed"},{"status":"live","beskrivning":"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.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"djurdata","sokvag":"/api/v1/cdb/:ssn/ppns_for_ssn"},{"status":"live","beskrivning":"Samma som ppns_for_ssn, fast med hela djurlistan per anläggning. Tyngre anrop.","kraver":["Authorization","BusinessUuid"],"metod":"GET","omrade":"djurdata","sokvag":"/api/v1/cdb/:ssn/ppns_with_animals_for_ssn"},{"status":"live","beskrivning":"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.","kraver":["Authorization"],"metod":"GET","omrade":"drift","sokvag":"/api/v1/last_file_date"}],"fallgropar":[{"sak":"Lagra djurets uuid, inte öronnumret","varfor":"Ö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."},{"sak":"En anläggning har två adresser - blanda inte ihop dem","varfor":"`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."},{"sak":"Person- och organisationsnummer förekommer i både tio- och tolvsiffrig form","varfor":"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."},{"sak":"En tom djurlista betyder inte alltid noll djur","varfor":"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."},{"sak":"Döda och avförda djur ligger kvar en tid","varfor":"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."},{"sak":"avliden_datum kan ligga långt bak i tiden på ett djur du får idag","varfor":"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."},{"sak":"Vilka fält du får se styrs av kundavtalet","varfor":"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."},{"sak":"POST /members är en upsert, inte ett create","varfor":"Samma personnummer två gånger ger en uppdatering (200), inte en dubblett (201). Kör den så ofta du vill."}],"fel":{"retry":"4xx ska inte retryas - samma anrop ger samma svar. 5xx och nätverkstimeout är den enda rimligt retrybara klassen. Ingen Retry-After skickas.","gren_pa_code":"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'.","koder":[{"betyder":"Authorization saknas eller matchar ingen kund.","kod":"invalid_token"},{"betyder":"BusinessUuid saknas eller är okänd.","kod":"missing_business"},{"betyder":"Din kund är inte kopplad till den verksamheten.","kod":"no_customer_business_link"},{"betyder":"Verksamheten har ingen koppling till den medlemmen.","kod":"no_business_member_link"},{"betyder":"Kopplingen finns men medlemmen har inget aktivt samtycke.","kod":"no_consent"},{"betyder":"Personnumret går inte att tolka.","kod":"invalid_ssn"},{"betyder":"Ingen medlem med det numret.","kod":"member_not_found"},{"betyder":"Inget djur med det uuid:t hos den medlemmen.","kod":"animal_not_found"}],"kuvertet_ar_inte_enhetligt":"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."},"kom_igang":[{"anrop":null,"rubrik":"Teckna avtal med Jordbruksverket","steg":1,"vad":"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."},{"anrop":null,"rubrik":"Få en API-nyckel av kossorna","steg":2,"vad":"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."},{"anrop":"POST /api/v1/businesses","rubrik":"Onboarda verksamheten","steg":3,"vad":"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."},{"anrop":"POST /api/v1/members","rubrik":"Lägg till djurhållaren","steg":4,"vad":"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."},{"anrop":"GET /api/v1/members/{ssn}/find_by_ssn","rubrik":"Vänta in samtycket","steg":5,"vad":"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."},{"anrop":"GET /api/v1/cdb/{ssn}/ppns_for_ssn","rubrik":"Hämta anläggningarna","steg":6,"vad":"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."},{"anrop":"GET /api/v1/cdb/{ssn}/ppns_with_animals_for_ssn","rubrik":"Hämta djuren","steg":7,"vad":"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."},{"anrop":"GET /api/v1/cdb/{ssn}/dead · GET /api/v1/cdb/{ssn}/departed · GET /api/v1/last_file_date","rubrik":"Håll dig uppdaterad","steg":8,"vad":"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."}],"kontrakt":{"basadress":{"lokal_utveckling":"http://localhost:4000","produktion":"https://kossorna.se"},"fullstandigt_kontrakt":"https://kossorna.se/api/kossorn_openapi.yaml (OpenAPI 3.1)","regel":"Menyn förklarar modellen och fallgroparna. Fältnamn, scheman och exakta felkuvert står i OpenAPI-filen, och den vinner vid konflikt."},"modell":{"begrepp":{"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."},"hierarki":["kund - journalsystemet självt, det som håller API-nyckeln","verksamhet (business) - veterinärpraktiken eller motsvarande, identifieras av BusinessUuid","medlem - djurhållaren, identifieras av person- eller organisationsnummer","anläggning - platsen där djuren står","djur - det enskilda nötkreaturet"]},"samtycke":{"regel":"Ingen djurdata utan att djurhållaren gett verksamheten samtycke. Saknas det svarar /cdb 401 med code `no_consent`.","atervandande":"`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.","sms_fallback":"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.","varianter":["BankID-signering (bankid_order_ref)","FrejaID-signering (freja_sign_ref)","Uppladdat signerat dokument (fil)"],"vem_far_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."},"syfte":"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.","tjanst":"Kossorna CDB-API","webhook":{"form":"{webhook_url}/{VERKSAMHETENS_SSN}/business/{MEDLEMMENS_SSN}/member","retry":"Första anropet plus tre omförsök: efter 10 minuter, 2 timmar och 24 timmar.","vad":"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.","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.","konfiguration":"webhook_url sätts per kund, internt hos kossorna - det finns ingen self-service.","kontext_i_headers":["X-Customer-Id","X-Member-SSN","User-Agent: Kossorna.se/1.0"],"triggas_av":["medlem skapad eller uppdaterad","samtycke godkänt eller signerat","SMS-status från operatören"]}}