Kiban platform API (1.0.0)

Download OpenAPI specification:

The gateway-owned "platform" surface: every /api/* route mounted directly by internal/gateway itself (foundation_routes.go, platform_routes.go, admin_position_routes.go, admin_group_routes.go), as opposed to a module's own /api/<moduleKey>/* fragment. This is not a module: there is no module.manifest.json/ authz.fragment.json for the platform; it is documented here because the module contract's binding rule ("the running service must enforce this contract at the HTTP boundary") applies equally to the platform's own consumer-facing surface, so a developer can use Kiban from docs alone. Every route here is a fixed reverse proxy (never the dynamic, opaque module-key proxy in proxy.go): the gateway rewrites the outbound path to an internal /internal/... route on one of the four foundation services (authz, org, registry) and, for the self-scoped reads, forces the outbound kcSub to the validated bearer's own subject (never a client-supplied value: see foundation_routes.go's own doc comment). All routes require a valid bearer (RequireAuth); routes under .../admin/... additionally require the superadmin guard (RequireSuperadmin): a fail-closed check: if authz itself is unreachable, the admin guard returns 503 AUTHORIZATION_UNAVAILABLE, never a silent allow. Validated the same way a module's own fragment is (the internal/testopenapi helper, github.com/getkin/kin-openapi): internal/gateway/platform_openapi_live_test.go drives real requests through a live kiban-test gateway and validates the recorded response against this fragment's declared schema: at least the happy path and one error per route, so this document is trustworthy, not aspirational.

Ask whether the caller (never an arbitrary actor) can do one thing

Bearer-gated fixed proxy to authz's own /internal/authz/effective-access/can (internal/authz/http.go). authz itself rejects a body naming a subject other than the bearer (400), and a global-scope request without requiredPlatformRole (400 VALIDATION_ERROR), the gateway adds nothing beyond bearer validation at the edge.

Authorizations:
bearerAuth
Request Body schema: application/json
required
featureKey
required
string
moduleKey
string
scope
required
string
Enum: "global" "company"
companyId
string <uuid>
requiredPlatformRole
string
allowPlatformOperatorCompanyScope
boolean
requiredCompanyRole
string
object (EffectiveAccessObjectRef)
relation
string
requiresEligibility
boolean
correlationId
string

Responses

Request samples

Content type
application/json
{
  • "featureKey": "string",
  • "moduleKey": "string",
  • "scope": "global",
  • "companyId": "8bb73d03-06b4-47c7-80c7-59301f770eda",
  • "requiredPlatformRole": "string",
  • "allowPlatformOperatorCompanyScope": true,
  • "requiredCompanyRole": "string",
  • "object": {
    },
  • "relation": "string",
  • "requiresEligibility": true,
  • "correlationId": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Ask whether the caller can do several things in one round trip

Same subject and scope rules as can, bearer-gated fixed proxy to authz's own /internal/authz/effective-access/batch-can. At most 100 items per request (400 VALIDATION_ERROR above); a request with denied items writes one audit event listing them (the first 50, with a truncated flag).

Authorizations:
bearerAuth
Request Body schema: application/json
required
required
Array of objects <= 100 items

Responses

Request samples

Content type
application/json
{
  • "items": [
    ]
}

Response samples

Content type
application/json
{
  • "data": [
    ]
}

The caller's own display-composed access summary (a display aid, never an enforcement surface)

Self-scoped, internal-network-unauthenticated downstream (authz's own /internal/authz/effective-access/summary, kcSub as a plain query param), the gateway is the bearer-validating boundary and forces kcSub to the validated bearer's own subject, overwriting any client-supplied value. objectAccess is direct-tuples-only (never a userset-closure expansion); a position/group-held tier never appears here even when it is real and enforced elsewhere.

Authorizations:
bearerAuth
query Parameters
companyId
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Companies where the caller has an active membership in an active company (the company switcher's source)

Self-scoped fixed proxy to org's own /internal/org/me/companies (internal/org/http.go's handleMeCompanies), kcSub forced to the bearer's own subject.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

The company's member directory (the least-disclosure share-with/assign-to picker source)

Bearer-gated fixed proxy to org's own /internal/org/companies/{companyId}/members, kcSub forced to the bearer's own subject. org's own handler enforces active-membership-or-superadmin (requireMemberOrAdmin), a non-member caller gets 403, not a 404 (never leaks whether the company exists to an outsider vs. a non-member).

Authorizations:
bearerAuth
path Parameters
companyId
required
string <uuid>
query Parameters
q
string
page
integer >= 1
Default: 1
pageSize
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Membership fact for one subject (app backend or superadmin)

Fixed proxy to org's /internal/org/companies/{companyId}/members/by-kcsub/{subject}. A registered app's backend (client-credentials token whose azp is the app's service client id) or a superadmin asks whether subject is an active member of the company and which member record that is. Never a directory: one subject, one answer.

Authorizations:
bearerAuth
path Parameters
companyId
required
string <uuid>
subject
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Write authz tuples (superadmin, or a registered app on its own types)

Fixed proxy to authz's own /internal/authz/grants (internal/authz/http.go), the one write path exposed through the gateway for raw tuple grants. A superadmin may call it; so may a registered app's backend (a client-credentials token whose azp is the app's service client id), which authz then restricts to tuples on that app's own object types.

Authorizations:
bearerAuth
Request Body schema: application/json
required
op
required
string
Enum: "grant" "revoke"
companyId
required
string <uuid>
correlationId
string
required
Array of objects

Responses

Request samples

Content type
application/json
{
  • "op": "grant",
  • "companyId": "8bb73d03-06b4-47c7-80c7-59301f770eda",
  • "correlationId": "string",
  • "tuples": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Grant the platform role (superadmin only)

Superadmin-gated fixed proxy to authz's own POST /internal/authz/platform-roles. The platform role's one record is the authz tuple system:platform#superadmin @ user:<subjectId>; authz writes it with its grant-ledger and audit rows in one transaction. The subject must already exist in identity (404 otherwise); kiban-superadmin is the only role (422 otherwise).

Authorizations:
bearerAuth
Request Body schema: application/json
required
subjectId
required
string

The subject's Keycloak subject id (kcSub).

role
required
string
Value: "kiban-superadmin"

Responses

Request samples

Content type
application/json
{
  • "subjectId": "string",
  • "role": "kiban-superadmin"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke the platform role (superadmin only; the last superadmin cannot be revoked)

Superadmin-gated fixed proxy to authz's own DELETE /internal/authz/platform-roles/{role}/{subjectId}. 404 when the subject does not hold the role; 409 CONFLICT when it is the platform's last superadmin (the caller revoking themselves included).

Authorizations:
bearerAuth
path Parameters
role
required
string
Value: "kiban-superadmin"
subjectId
required
string

The subject's Keycloak subject id (kcSub).

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Capability state for every module (installed/enabled/dependencies)

Bearer-gated only (deliberately outside the superadmin gate, avoids a gateway<->authz cycle, platform_routes.go's own header comment). Fixed proxy to the registry service, path forwarded unchanged.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Capability state for one module

Authorizations:
bearerAuth
path Parameters
module
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

The full module catalog (metadata for every known module, not just capability state)

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Enable a module (superadmin only)

Superadmin-gated fixed proxy, rewritten to the registry's own /internal/platform/modules/{key}/enable.

Authorizations:
bearerAuth
path Parameters
key
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Register an app from its manifest (superadmin only)

Superadmin-gated fixed proxy to the registry's /internal/platform/apps. Registers (or re-registers) an app that runs beside Kiban: its object types and relations (the authorization fragment), its feature keys and the service client whose token identifies its backend. The app is installed at once and enabled per company with the enable route. No restart; the gateway never proxies to it.

Authorizations:
bearerAuth
Request Body schema: application/json
required
key
required
string^[a-z][a-z0-9_]{1,31}$
displayName
required
string
version
required
string
serviceClientId
required
string^[a-z][a-z0-9_-]{1,63}$
features
Array of strings
required
object

The fragment's "relations" section, one entry per object type

Responses

Request samples

Content type
application/json
{
  • "key": "string",
  • "displayName": "string",
  • "version": "string",
  • "serviceClientId": "string",
  • "features": [
    ],
  • "authzFragment": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Disable a module (superadmin only; mandatory modules refuse)

Authorizations:
bearerAuth
path Parameters
key
required
string

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

The platform's aggregated Prometheus exposition (operator-only, one scrape target)

The only metrics surface exposed through the gateway, behind the superadmin guard, and the single scrape target for the whole platform: the gateway's own registry merged with the /metrics of registry, identity, org, authz and every installed+enabled module, fetched concurrently on the internal network (2 s per target). Every series carries a service="<name>" label; metric families of the same name from several services share one # HELP/# TYPE. A target that fails or times out contributes only kiban_metrics_scrape_up{service="<name>"} 0 (1 on success) — the response is always 200, never a failed scrape. Plain-text Prometheus exposition format (text/plain; version=0.0.4), not JSON, documented here with no schema.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Create a company or an org unit (superadmin only)

Superadmin-gated fixed proxy to org's /internal/org/units. A company is a unit with typeKey "company" and no parent; creating an active company also writes every enabled module's administrator relation for it in the same transaction. Any other unit names its parent. Codes are normalised to upper case.

Authorizations:
bearerAuth
Request Body schema: application/json
required
typeKey
required
string

"company" for a company; a taxonomy key such as "business_unit" below it

parentId
string or null <uuid>
code
required
string
name
required
string

Responses

Request samples

Content type
application/json
{
  • "typeKey": "string",
  • "parentId": "70850378-7d3c-4f45-91b7-942d4dfbbd43",
  • "code": "string",
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Read one org unit (superadmin only)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

The unit and every unit below it (superadmin only)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Create a member in a company (superadmin only)

Superadmin-gated fixed proxy to org's /internal/org/members. The member has no login until it is linked to a subject with the link-user route.

Authorizations:
bearerAuth
Request Body schema: application/json
required
companyId
required
string <uuid>
code
required
string
displayName
required
string
email
string

Responses

Request samples

Content type
application/json
{
  • "companyId": "8bb73d03-06b4-47c7-80c7-59301f770eda",
  • "code": "string",
  • "displayName": "string",
  • "email": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List a company's members (superadmin only)

Authorizations:
bearerAuth
query Parameters
companyId
required
string <uuid>
page
integer >= 1
Default: 1
pageSize
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a member (superadmin only)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
displayName
string
email
string
isActive
boolean

Responses

Request samples

Content type
application/json
{
  • "displayName": "string",
  • "email": "string",
  • "isActive": true
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Link a member to a login subject (superadmin only)

kcSub is the subject's sub claim; the identity record exists once that subject has made one request through the gateway. A subject is linked to at most one member per company.

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
kcSub
required
string

Responses

Request samples

Content type
application/json
{
  • "kcSub": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Unlink a member from its login subject (superadmin only)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List a company's positions, each with its current holder (superadmin only)

Authorizations:
bearerAuth
path Parameters
companyId
required
string <uuid>
query Parameters
page
integer >= 1
Default: 1
pageSize
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create a position (superadmin only)

Authorizations:
bearerAuth
path Parameters
companyId
required
string <uuid>
Request Body schema: application/json
required
code
required
string
title
required
string
orgUnitId
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "code": "string",
  • "title": "string",
  • "orgUnitId": "d6d5d992-9f70-470c-bee4-0571454bbbd7"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Assign a member to a position, effective now (superadmin only)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
memberId
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "memberId": "92983ab9-49c8-444b-85ae-6e40402cf72e"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

End a position assignment, effective now (superadmin only, no body, server-dated)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List a company's groups, each with its current member count (superadmin only)

Authorizations:
bearerAuth
path Parameters
companyId
required
string <uuid>
query Parameters
page
integer >= 1
Default: 1
pageSize
integer [ 1 .. 100 ]
Default: 25

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Create a group (superadmin only; source is always "kiban" through this route)

Authorizations:
bearerAuth
path Parameters
companyId
required
string <uuid>
Request Body schema: application/json
required
code
required
string
name
required
string

Responses

Request samples

Content type
application/json
{
  • "code": "string",
  • "name": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List a group's current members (superadmin only)

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Add a member to a group (superadmin only; 409 if the group is not source="kiban")

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
Request Body schema: application/json
required
memberId
required
string <uuid>

Responses

Request samples

Content type
application/json
{
  • "memberId": "92983ab9-49c8-444b-85ae-6e40402cf72e"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Remove a member from a group (superadmin only; 409 if the group is not source="kiban")

Authorizations:
bearerAuth
path Parameters
id
required
string <uuid>
memberId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}