Skip to Content

BI / Power BI

Mål: få in tidrapporteringsdatan i ditt data warehouse eller direkt i Power BI / Looker / Metabase via inkrementell synkronisering.

Rekommenderade scopes (endast läsning): org:fichajes:read, org:ausencias:read, org:saldos:read, org:estructura:read.

Använd en dedikerad read-only-nyckel för BI. Då kan du återkalla eller rotera den utan att påverka skrivande integrationer.

Mönster: inkrementell polling med updated_since

updated_since=<ISO8601> returnerar bara det som ändrats sedan den tidpunkten, men alla listor accepterar den inte: employees, check-ins, absences, locations, units och webhook-endpoints gör det; på vacation-balances och work-summaries accepteras den men filtrerar inte (föråldrad, tas bort 2027-08-27). Mönstret:

Initial laddning (backfill)

Gå igenom varje resurs och paginera med cursor tills has_more=false. Spara starttidpunkten som vattenmärke (watermark).

Inkrementella laddningar

Vid varje schemalagd körning, skicka ?updated_since=<watermark> och uppdatera ditt vattenmärke till tidpunkten strax före att anropet startade.

Deduplicera på id

Eftersom updated_since bygger på updated_at kan samma post dyka upp igen om den har ändrats. Gör upsertid (UUID) i ditt lager.

Ladda om aggregaten per intervall

vacation-balances och work-summaries filtrerar inte på updated_since: de är aggregat, det finns inget delta att be om. Fråga om intervallet du behöver vid varje körning — year= för saldon, from/to för summeringar — och gör upsert på varje resurss verkliga nyckel: (employee_id, period_start, period_end) för work-summaries och (employee_id, year) för vacation-balances. För ett månadsavslut räcker det att ladda om innevarande och föregående månad.

Exempel i Python

import os, requests from datetime import date, datetime, timedelta, timezone BASE = os.environ["KINMU_BASE_URL"] KEY = os.environ["KINMU_API_KEY"] SYNC_START = date(2026, 1, 1) # början på den historik du bryr dig om session = requests.Session() session.headers.update({"Authorization": f"Bearer {KEY}"}) def sync(resource, since=None): rows, cursor = [], None params = {"limit": 100} if since: params["updated_since"] = since while True: if cursor: params["cursor"] = cursor r = session.get(f"{BASE}/{resource}", params=params, timeout=30) r.raise_for_status() body = r.json() rows.extend(body["data"]) if not body["meta"]["has_more"]: break cursor = body["meta"]["next_cursor"] return rows def first_day_of_previous_month(day): first = day.replace(day=1) return (first - timedelta(days=1)).replace(day=1) # Vattenstämpel FÖRE anropet, så att poster som skrivs under synken inte tappas watermark = datetime.now(timezone.utc).isoformat() today = datetime.now(timezone.utc).date() last_watermark = load_last_watermark() # ISO 8601 | None employees = sync("employees", since=last_watermark) # Fönstret `from`/`to` avgränsar VILKA händelser du vill ha (stämplingens `timestamp`). # `updated_since` avgränsar VILKA ÄNDRINGAR som hämtas (`updated_at`): etapper utan ändringar kommer tomma. # Det är två olika axlar, och därför startar intervallet alltid vid SYNC_START: en rättelse som görs # idag på en stämpling från januari kommer bara med om januarietappen fortfarande ligger i fönstret. checkins = [] chunk_from = SYNC_START while chunk_from <= today: chunk_to = min(chunk_from + timedelta(days=91), today) checkins += sync(f"check-ins?from={chunk_from}&to={chunk_to}", since=last_watermark) chunk_from = chunk_to + timedelta(days=1) # Aggregat: utan `updated_since` laddas hela intervallet om. Börja på första dagen i # FÖREGÅENDE MÅNAD (i januari alltså föregående år) för att fånga sena konsolideringar # av det förra avslutet. previous_month = first_day_of_previous_month(today) balances = [] for year in sorted({previous_month.year, today.year}): balances += sync(f"vacation-balances?year={year}") summaries = sync(f"work-summaries?period=month&from={previous_month}&to={today}") save_watermark(watermark)

Ta vattenmärket innan synkroniseringen börjar, inte efteråt. Då kommer poster som skrivs medan processen kör med i nästa körning.

Användbara resurser för BI

ResursGer
work-summariesArbetstidsmetrik redo för aggregering (timmar, övertid, natt). updated_since filtrerar inte här (föråldrad): ladda om per intervall.
check-insHändelsenivå för analys av närvaro och punktlighet.
absencesFrånvaro per typ och period.
vacation-balancesSemestersaldon och avsättningar. updated_since filtrerar inte här (föråldrad): ladda om per year.
locations / unitsDimensioner att segmentera på (plats, avdelning).

Anslut från Power BI

Power BI kan konsumera API:et direkt med Web.Contents och en authorization-header. Förenklat exempel i Power Query (M):

let BaseUrl = "https://api.kinmu.app/v1", ApiKey = "kinmu_sk_live_…", // usa Parámetros / almacén de credenciales, no lo escribas en claro Source = Json.Document( Web.Contents(BaseUrl, [ RelativePath = "work-summaries", Query = [ period = "month", from = "2026-01-01", #"to" = "2026-12-31", limit = "100" ], Headers = [ Authorization = "Bearer " & ApiKey, Accept = "application/json" ] ]) ), Data = Source[data], Table = Table.FromRecords(Data) in Table

För att paginera i Power Query, kapsla anropet i en funktion som följer meta.next_cursor med List.Generate tills has_more är false.

Respektera rate limits: för stora volymer, schemalägg uppdateringen utanför rusningstid och håll koll på X-Kinmu-Quota-Remaining.

Eventdrivet alternativ

Vill du slippa polla? Prenumerera på webhooks (checkin.created, absence.approved, vacation_balance.updated, …) och uppdatera ditt lager när varje event kommer in. Kombinationen fungerar bra: webhooks för realtid + en nattlig polling med updated_since som skyddsnät.

Last updated on