Siirry sisältöön

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.

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.

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.

Kaikki reitit ovat /admin/*-polun alla, HMAC-allekirjoitettuja ja tenant-scopattuja (kutsuva X-Client-Id määrittää tenantin implisiittisesti isäntänimen kautta).

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).
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.
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ä
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
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.

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.

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.

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>"}.

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.

  • OIDC discovery: /.well-known/openid-configuration
  • OpenAPI: /openapi/machine-api.yaml ja /.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.