Machine API (M2M)
Machine API on tarkoitettu luotettujen taustapalveluiden käyttöön — esimerkiksi käyttäjien luontiin/synkronointiin, organisaatioiden ja roolien hallintaan sekä clienttien provisiointiin ilman ihmisen tekemää admin-paneelin käyttöä. Se ei käytä selainevästeitä eikä OAuth-bearer- tokeneita: jokainen pyyntö allekirjoitetaan HMAC-SHA256:lla.
Autentikointi
Osio nimeltä “Autentikointi”Jokaisessa pyynnössä on kolme pakollista headeria:
| Header | Arvo |
|---|---|
X-Client-Id |
Tenantin sisäinen OAuth-clientin client_id, jolle on luotu HMAC-avain |
X-Timestamp |
Unix-aika sekunteina. Palvelin hyväksyy vain ±30 sekunnin kellopoikkeaman |
X-Signature |
Pienaakkosin hex-koodattu HMAC-SHA256-allekirjoitus |
Valinnainen neljäs header, X-Nonce (8–64 merkkiä, [A-Za-z0-9_-]), erottaa
saman sekunnin sisällä lähetetyt samanlaiset pyynnöt toisistaan.
Kanoninen allekirjoitusmerkkijono (ilman noncea):
{METHOD}\n{PATH}\n{TIMESTAMP}\n{hex(sha256(BODY))}Nonce-versio:
{METHOD}\n{PATH}\n{TIMESTAMP}\n{NONCE}\n{hex(sha256(BODY))}PATH on pyynnön polku ilman kyselymerkkijonoa (esim. /admin/users/upsert).
BODY on tarkalleen lähetetty JSON-runko — tyhjä runko (GET-pyynnöt)
tiivistetään tyhjästä tavutaulukosta. Allekirjoitus lasketaan HMAC-avaimen
raakatavuilla; avain jaetaan hex-koodattuna, joten dekoodaa se tavuiksi
ennen allekirjoitusta.
Jokainen allekirjoitus on kertakäyttöinen: palvelin tallentaa sen
Redis-nonceksi 120 sekunnin ajaksi. Sama (timestamp, signature)-pari
toistettuna hylätään toistohyökkäyksenä — generoi tuore timestamp jokaiselle
yrityskerralle (myös uudelleenyrityksille).
Allekirjoitusfunktio (TypeScript, viitetoteutus)
Osio nimeltä “Allekirjoitusfunktio (TypeScript, viitetoteutus)”/** * signing_string = "{METHOD}\n{PATH}\n{UNIX_SECONDS}\n{hex(sha256(BODY))}" * X-Signature = hex(hmac-sha256(key, signing_string)) */
async function importKey(hexKey: string): Promise<CryptoKey> { const bytes = new Uint8Array(hexKey.match(/.{2}/g)!.map((h) => parseInt(h, 16))); return crypto.subtle.importKey("raw", bytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);}
async function sha256Hex(data: Uint8Array): Promise<string> { const buf = await crypto.subtle.digest("SHA-256", data); return toHex(buf);}
async function hmacHex(key: CryptoKey, message: string): Promise<string> { const buf = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message)); return toHex(buf);}
function toHex(buf: ArrayBuffer): string { return Array.from(new Uint8Array(buf)) .map((b) => b.toString(16).padStart(2, "0")) .join("");}
export async function signedPost( baseUrl: string, clientId: string, hmacKeyHex: string, path: string, body: unknown,): Promise<Response> { const bodyBytes = new TextEncoder().encode(JSON.stringify(body)); const timestamp = String(Math.floor(Date.now() / 1000)); const bodyHash = await sha256Hex(bodyBytes); const signingString = `POST\n${path}\n${timestamp}\n${bodyHash}`;
const key = await importKey(hmacKeyHex); const signature = await hmacHex(key, signingString);
return fetch(`${baseUrl}${path}`, { method: "POST", headers: { "Content-Type": "application/json", "X-Client-Id": clientId, "X-Timestamp": timestamp, "X-Signature": signature, }, body: bodyBytes, });}Käyttää vain Web Crypto -rajapintaa (globalThis.crypto) — toimii
Node.js 18+:ssa ja Cloudflare Workersissa ilman riippuvuuksia. Avainta ei saa
koskaan toimittaa selaimeen tai mobiilisovellukseen.
Täysi paketti (mukaan lukien upsertUser- ja verifySigningSetup-kääreet):
Koodiesimerkit.
Oikeudet (machine_permissions)
Osio nimeltä “Oikeudet (machine_permissions)”Clientin machine_permissions-kenttä on välilyönnillä eroteltu lista
tokeneita, jotka rajaavat mitä reittejä
client saa kutsua. Jos kenttä on tyhjä (null), client on rajoittamaton
ja saa kutsua kaikkia machine-reittejä — uusille clienteille suositellaan
aina eksplisiittistä, minimaalista oikeusjoukkoa.
| Token | Antaa oikeuden |
|---|---|
identity |
Kaikki /admin/identity/*-reitit |
clients:manage |
POST /admin/clients/create, POST /admin/clients/update |
users:manage |
POST /admin/users/upsert |
sms:send |
POST /admin/sms/send |
hmac:echo |
POST /admin/hmac/echo |
branding:write |
POST /admin/tenant/branding/update |
createServiceClient/updateServiceClient-kutsuissa uuden/päivitetyn
clientin machine_permissions-arvon täytyy olla kutsuvan clientin oman
oikeusjoukon osajoukko — rajoitettu caller ei voi luoda itseään
laajemmin oikeutettua clientia.
Reitit
Osio nimeltä “Reitit”Kaikki reitit ovat /admin/*-polun alla, HMAC-allekirjoitettuja ja
tenant-scopattuja (kutsuva X-Client-Id määrittää tenantin implisiittisesti
isäntänimen kautta).
Clientit ja brändäys (legacy-reitit)
Osio nimeltä “Clientit ja brändäys (legacy-reitit)”| Metodi | Polku | Käyttötarkoitus |
|---|---|---|
| POST | /admin/clients/create |
Luo uuden OAuth-clientin samaan tenanttiin. Palauttaa client_secret+hmac_secret kertaalleen. |
| POST | /admin/clients/update |
Päivittää olemassa olevan clientin redirect-URIt, nimen, backchannel-URIn tai machine_permissions-arvon. |
| POST | /admin/tenant/branding/update |
Päivittää tenantin brändäyskentät (näyttönimi, logo, väri, tukisähköposti). |
Käyttäjät (legacy)
Osio nimeltä “Käyttäjät (legacy)”| Metodi | Polku | Käyttötarkoitus |
|---|---|---|
| POST | /admin/users/upsert |
Luo tai päivittää käyttäjän sähköpostilla. Palauttaa { sub }. Uusi Identity API -versio alla on tätä kattavampi ja säilytetään yhteensopivuuden vuoksi. |
| POST | /admin/sms/send |
Lähettää SMS:n annettuun numeroon tai käyttäjän tallennettuun puhelinnumeroon. |
| POST | /admin/hmac/echo |
Debug-reitti: kaikuttaa pyynnön rungon ja kutsuvan clientin ID:n takaisin. Ei sivuvaikutuksia — käytä allekirjoituksen testaamiseen. |
Käyttäjät (Identity API)
Osio nimeltä “Käyttäjät (Identity API)”| Metodi | Polku | Käyttötarkoitus |
|---|---|---|
| GET | /admin/identity/users/{sub} |
Hae käyttäjä + profiili UUID:llä |
| GET | /admin/identity/users/by-email/{email} |
Hae käyttäjä sähköpostilla |
| POST | /admin/identity/users/upsert |
Luo/päivitä käyttäjä sähköpostilla, profiilikentät mukaan lukien |
| GET | /admin/identity/users/{sub}/profile |
Hae laajennettu profiili |
| PATCH | /admin/identity/users/{sub}/profile |
Päivitä profiilikenttiä |
| GET | /admin/identity/users/{sub}/orgs |
Käyttäjän organisaatiojäsenyydet |
| GET | /admin/identity/users/{sub}/roles |
Käyttäjän kaikki roolimääritykset (globaalit + org-scopatut) |
| POST | /admin/identity/users/{sub}/roles |
Määritä rooli käyttäjälle (luo roolin tarvittaessa; idempotentti) |
| DELETE | /admin/identity/users/{sub}/roles/{role_name} |
Poista roolimääritys (?org_id= org-scopattuun) |
Organisaatiot, jäsenet, kutsut, ryhmät (Identity API)
Osio nimeltä “Organisaatiot, jäsenet, kutsut, ryhmät (Identity API)”| Metodi | Polku | Käyttötarkoitus |
|---|---|---|
| POST | /admin/identity/orgs |
Luo organisaatio (name, valinn. business_id, owner_sub) |
| GET | /admin/identity/orgs/by-business-id/{business_id} |
Hae organisaatio Y-tunnuksella |
| GET | /admin/identity/orgs/{org_id} |
Hae organisaatio |
| PATCH | /admin/identity/orgs/{org_id} |
Päivitä name/business_id |
| GET | /admin/identity/orgs/{org_id}/members |
Listaa jäsenet |
| POST | /admin/identity/orgs/{org_id}/members |
Lisää jäsen (user_id, role) |
| DELETE | /admin/identity/orgs/{org_id}/members/{user_id} |
Poista jäsen |
| POST | /admin/identity/orgs/{org_id}/invitations |
Luo kutsu (sähköposti tai puhelin) — tunnuspalvelu ei lähetä itse kutsuviestiä, ks. alla |
| GET | /admin/identity/orgs/{org_id}/groups |
Listaa organisaation ryhmät |
| POST | /admin/identity/orgs/{org_id}/groups |
Luo ryhmä |
| POST | /admin/identity/orgs/{org_id}/groups/{group_id}/members |
Lisää jäsen ryhmään |
| DELETE | /admin/identity/orgs/{org_id}/groups/{group_id}/members/{user_id} |
Poista jäsen ryhmästä |
Sessiot
Osio nimeltä “Sessiot”| Metodi | Polku | Käyttötarkoitus |
|---|---|---|
| POST | /admin/identity/whoami |
Validoi __Host-sid-evästeen arvo ja palauttaa käyttäjätiedot, roolit ja org-jäsenyydet yhdellä kutsulla |
Pyyntö- ja vastausesimerkit
Osio nimeltä “Pyyntö- ja vastausesimerkit”upsertUser (Identity API)
Osio nimeltä “upsertUser (Identity API)”POST /admin/identity/users/upsert{ "email": "kayttaja@esimerkki.fi", "name": "Matti Meikäläinen", "phone": "+358401234567", "profile": { "locale": "fi" }}Vastaus:
{ "sub": "01923f7a-...", "email": "kayttaja@esimerkki.fi", "name": "Matti Meikäläinen", "phone": "+358401234567", "is_new": true, "is_admin": false, "profile": { "locale": "fi" }}Idempotentti sähköpostin mukaan: sama sähköposti toisella kutsulla päivittää
olemassa olevan käyttäjän (is_new: false) sen sijaan, että loisi
duplikaatin. Luotu käyttäjä merkitään email_verified: true, koska kutsuja
on luotettu machine-client.
whoami
Osio nimeltä “whoami”POST /admin/identity/whoami{ "sid": "<selaimen __Host-sid-evästeen raakaan arvo>" }Vastaa 401, jos sid puuttuu, on tyhjä, ei vastaa tallennettua sessiota tai
sessio on vanhentunut/peruutettu. Vastaus (200):
{ "sub": "01923f7a-...", "email": "kayttaja@esimerkki.fi", "email_verified": true, "name": "Matti Meikäläinen", "phone": "+358401234567", "phone_verified": true, "is_admin": false, "is_platform_admin": false, "created_at": "2026-01-10T09:00:00Z", "updated_at": "2026-06-01T12:00:00Z", "profile": null, "roles": ["billing_admin"], "orgs": [ { "org_id": "5e3b1a2c-...", "name": "Esimerkki Oy", "role": "admin", "roles": [] } ], "session_expires_at": "2026-07-01T09:00:00Z", "session_last_active_at": "2026-06-20T08:30:00Z"}whoami on Identity API:n suurimman kutsumäärän reitti — jokainen
kuluttajapalvelun autentikoitu pyyntö tyypillisesti alkaa tästä. Kerroksista
oma välimuisti (esim. lyhyt TTL avaimella sha256(sid)) suositellaan, jos
kutsutaajuus on korkea.
assignUserRole
Osio nimeltä “assignUserRole”POST /admin/identity/users/{sub}/roles{ "role_name": "billing_admin" }Lisää "org_id": "<uuid>" org-scopattuun määritykseen. Luo roolin
automaattisesti tenanttiin, jos sitä ei vielä ole. Idempotentti — sama
(role_name, org_id)-pari kahdesti on turvallinen. Palauttaa 204 No Content.
Virhekoodit
Osio nimeltä “Virhekoodit”| HTTP | Merkitys | Toimenpide |
|---|---|---|
400 |
Virheellinen runko, kelvoton URI, tai keskenään ristiriitaiset kentät | Korjaa pyyntö — älä yritä uudelleen muuttamatta sitä |
401 |
Puuttuva/virheellinen HMAC-header, vanhentunut timestamp, väärä allekirjoitus tai toisto | Allekirjoita uudelleen tuoreella timestampilla; jos yhäkin epäonnistuu, tarkista avain |
403 |
Kutsuvalta clientilta puuttuu vaadittu machine_permissions-token, tai se yritti myöntää oikeuden jota sillä itsellään ei ole |
Käytä credentiaalia, jolla on tarvittava oikeus |
404 |
Viitattua käyttäjää tai tenant-scopattua objektia ei löytynyt | Tarkista tunnisteet |
413 |
Pyynnön runko ylittää HMAC-todennuksen kokorajan (1 MiB) | Pienennä pyyntöä |
500 |
Palvelinvirhe | Yritä uudelleen tavanomaisella eksponentiaalisella viiveellä |
Kaikki virhevastaukset ovat JSON-muotoa {"error": "<viesti>"}.
Organisaatiokutsujen lähettäminen
Osio nimeltä “Organisaatiokutsujen lähettäminen”POST /admin/identity/orgs/{org_id}/invitations luo kutsutietueen
(kertakäyttöinen token; voimassaolo oletuksena 7 vuorokautta, säädettävissä pyynnön kentällä expires_in_days) mutta tunnuspalvelu
ei lähetä itse kutsusähköpostia/-SMS:ää — kutsuviestin lähettäminen jää
kutsuvan palvelun vastuulle. Käytä vastauksen token-kenttää linkin
rakentamiseen omassa sovelluksessasi.
Machine-readable kuvaukset
Osio nimeltä “Machine-readable kuvaukset”- OIDC discovery:
/.well-known/openid-configuration - OpenAPI:
/openapi/machine-api.yamlja/.well-known/tunnuspalvelu-machine-api.openapi.yaml - Agenteille suunnattu opas:
/llms.txt
Käytä tätä sivua ensisijaisena lähteenä Identity API:n org-/ryhmä-/rooli- reiteille — OpenAPI-kuvaus kattaa tällä hetkellä ensisijaisesti legacy- reitit ja käyttäjien perustoiminnot.
Katso myös
Osio nimeltä “Katso myös”- Koodiesimerkit — täysi TypeScript-paketti
- Webhookit — tapahtumaviestit toiseen suuntaan
- Organisaatiot — org-/ryhmä-/rooli-malli tarkemmin