openapi: 3.1.0
info:
  title: Kioo API
  version: "1.0.0"
  description: |
    Read-only HTTP API over Kioo's team graph.

    Exposes the organization's structure — org units, teams, each team's
    profile, the people on them and their roles, and the dependencies between
    teams. It mirrors what any member already sees inside Kioo; it never
    exposes more.

    Design notes
    ------------
    * Every request is scoped to a single organization by the bearer token.
      The token *is* the tenant boundary — there is no `orgId` parameter and
      no way to read another organization's data.
    * All endpoints are read-only (GET). There are no write operations in v1.
    * Related resources are embedded as `{ id, name }` reference stubs so a
      consumer rarely needs a second call. Full objects live at their own
      endpoint.
    * A team's `profile` only ever contains the fields that team chose to
      publish; fields the team hid in Kioo are omitted from the response.
    * NOT in v1: Team Health, strengths, surveys, team-contract free-text.
  contact:
    name: Kioo
    url: https://docs.kioo.app

servers:
  - url: https://api.kioo.app/v1

security:
  - bearerAuth: []

tags:
  - name: Structure
  - name: People
  - name: Graph

paths:
  /organization:
    get:
      tags: [Structure]
      operationId: getOrganization
      summary: The organization this token belongs to
      description: Also serves as a "whoami" root for the token.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Organization" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /org-units:
    get:
      tags: [Structure]
      operationId: listOrgUnits
      summary: The org-unit hierarchy
      description: |
        Nodes in the organization's structure. Each node has a `parent_id`
        (null at the top) and a `type` naming its layer (e.g. Division,
        Value Stream, Cluster).
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/OrgUnit" }
                  next_cursor: { $ref: "#/components/schemas/NextCursor" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /org-unit-types:
    get:
      tags: [Structure]
      operationId: listOrgUnitTypes
      summary: The named layers of the org-unit hierarchy
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/OrgUnitType" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /team-types:
    get:
      tags: [Structure]
      operationId: listTeamTypes
      summary: Team type definitions
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TeamType" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /teams:
    get:
      tags: [Structure]
      operationId: listTeams
      summary: List all teams (the catalogue)
      description: A light summary per team. Use `GET /teams/{teamId}` for full detail.
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
        - name: org_unit_id
          in: query
          description: Filter to teams under one org unit.
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TeamSummary" }
                  next_cursor: { $ref: "#/components/schemas/NextCursor" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /teams/{teamId}:
    get:
      tags: [Structure]
      operationId: getTeam
      summary: One team in full
      description: |
        Includes the team's profile, its members (with team role and functional
        roles), links, and its dependencies framed relative to this team.
      parameters:
        - { $ref: "#/components/parameters/TeamId" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Team" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /teams/{teamId}/members:
    get:
      tags: [People]
      operationId: listTeamMembers
      summary: The people on a team
      parameters:
        - { $ref: "#/components/parameters/TeamId" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Member" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /teams/{teamId}/dependencies:
    get:
      tags: [Graph]
      operationId: listTeamDependencies
      summary: A team's dependencies (framed relative to this team)
      description: The same edges are also embedded in `GET /teams/{teamId}`.
      parameters:
        - { $ref: "#/components/parameters/TeamId" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/TeamDependency" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /people:
    get:
      tags: [People]
      operationId: listPeople
      summary: List everyone in the organization
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PersonSummary" }
                  next_cursor: { $ref: "#/components/schemas/NextCursor" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /people/{personId}:
    get:
      tags: [People]
      operationId: getPerson
      summary: One person and every team they're on
      parameters:
        - name: personId
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Person" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /roles:
    get:
      tags: [People]
      operationId: listRoles
      summary: Functional role definitions
      description: |
        The organization's functional roles (e.g. Tech Lead, Product Owner).
        A `roles[]` entry on a member/person references one of these by id.
        Distinct from `team_role` (admin / coach / member).
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Role" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

  /dependencies:
    get:
      tags: [Graph]
      operationId: listDependencies
      summary: The whole org dependency graph
      description: Every edge, in absolute `from_team` / `to_team` terms.
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/GraphDependency" }
                  next_cursor: { $ref: "#/components/schemas/NextCursor" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/TooManyRequests" }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        A Kioo API key. Send it as `Authorization: Bearer kioo_...`.
        Read-only, scoped to one organization, created by any member under
        Settings -> Integrations -> API keys. Shown once at creation; revocable.

  parameters:
    Limit:
      name: limit
      in: query
      description: >-
        Reserved. Not yet enforced in v1 — list endpoints currently return all
        rows up to a safe cap and always report `next_cursor: null`.
      schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
    Cursor:
      name: cursor
      in: query
      description: Reserved (see `limit`). Opaque cursor for a future paginated release.
      schema: { type: string }
    TeamId:
      name: teamId
      in: path
      required: true
      schema: { type: string, format: uuid }

  schemas:
    Ref:
      type: object
      description: A lightweight reference to another resource.
      required: [id, name]
      properties:
        id: { type: string, format: uuid }
        name: { type: string }

    NextCursor:
      type: [string, "null"]
      description: Cursor for the next page, or null if this is the last page.

    Organization:
      type: object
      properties:
        object: { const: organization }
        id: { type: string, format: uuid }
        name: { type: string }
        slug: { type: string }

    OrgUnitType:
      type: object
      description: A named layer of the org-unit hierarchy.
      properties:
        object: { const: org_unit_type }
        id: { type: string, format: uuid }
        name: { type: string, description: "Resolved to the default locale (English)." }
        level: { type: integer, description: "Ordering; lower is higher in the hierarchy." }

    OrgUnit:
      type: object
      properties:
        object: { const: org_unit }
        id: { type: string, format: uuid }
        name: { type: string }
        parent_id:
          type: [string, "null"]
          format: uuid
          description: The parent org unit, or null for a top-level unit.
        type:
          oneOf:
            - { $ref: "#/components/schemas/Ref" }
            - { type: "null" }
          description: The layer this unit sits at.

    TeamType:
      type: object
      properties:
        object: { const: team_type }
        id: { type: string, format: uuid }
        name: { type: string }
        color: { type: [string, "null"] }

    Profile:
      type: object
      description: |
        The team's descriptive profile. Only fields the team chose to publish
        are present — hidden fields are omitted entirely.
      properties:
        purpose: { type: [string, "null"] }
        scope: { type: [string, "null"] }
        self_service: { type: [string, "null"] }
        work_style: { type: [string, "null"], enum: ["in-person", "remote", "mix", null] }
        task_type: { type: [string, "null"], enum: ["development", "operations", "both", null] }
        maturity: { type: [string, "null"], enum: ["new", "settled", "long_running", null] }
        location: { type: [string, "null"] }
        time_allocation:
          type: [object, "null"]
          description: Percentages, roughly summing to 100.
          properties:
            create_value: { type: integer }
            protect_value: { type: integer }
            overhead: { type: integer }

    Link:
      type: object
      properties:
        label: { type: string }
        url: { type: string, format: uri }

    Role:
      type: object
      description: A functional role definition (what a person does).
      properties:
        object: { const: role }
        id: { type: string, format: uuid }
        name: { type: string }
        description: { type: [string, "null"] }

    Member:
      type: object
      description: A person as they appear on a team.
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        email: { type: [string, "null"], format: email }
        team_role:
          type: string
          enum: [admin, coach, member]
          description: The person's standing on this team.
        roles:
          type: array
          description: Functional roles this person holds on this team.
          items: { $ref: "#/components/schemas/Ref" }

    TeamSummary:
      type: object
      description: A team as it appears in the catalogue listing.
      properties:
        object: { const: team }
        id: { type: string, format: uuid }
        name: { type: string }
        slug: { type: string }
        kind: { type: string, enum: [regular, placeholder, external] }
        type:
          oneOf: [{ $ref: "#/components/schemas/Ref" }, { type: "null" }]
        org_unit:
          oneOf: [{ $ref: "#/components/schemas/Ref" }, { type: "null" }]
        member_count: { type: integer }

    Team:
      type: object
      description: Full team detail.
      properties:
        object: { const: team }
        id: { type: string, format: uuid }
        name: { type: string }
        slug: { type: string }
        kind: { type: string, enum: [regular, placeholder, external] }
        type:
          oneOf: [{ $ref: "#/components/schemas/Ref" }, { type: "null" }]
        org_unit:
          type: [object, "null"]
          description: The org unit this team sits in, with its layer type.
          properties:
            id: { type: string, format: uuid }
            name: { type: string }
            type:
              oneOf: [{ $ref: "#/components/schemas/Ref" }, { type: "null" }]
        profile: { $ref: "#/components/schemas/Profile" }
        links:
          type: array
          items: { $ref: "#/components/schemas/Link" }
        member_count: { type: integer }
        members:
          type: array
          items: { $ref: "#/components/schemas/Member" }
        dependencies:
          type: array
          description: Dependencies touching this team, framed relative to it.
          items: { $ref: "#/components/schemas/TeamDependency" }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    Membership:
      type: object
      description: One of a person's team memberships.
      properties:
        team: { $ref: "#/components/schemas/Ref" }
        team_role: { type: string, enum: [admin, coach, member] }
        roles:
          type: array
          items: { $ref: "#/components/schemas/Ref" }

    PersonSummary:
      type: object
      properties:
        object: { const: person }
        id: { type: string, format: uuid }
        name: { type: string }
        email: { type: [string, "null"], format: email }

    Person:
      type: object
      properties:
        object: { const: person }
        id: { type: string, format: uuid }
        name: { type: string }
        email: { type: [string, "null"], format: email }
        memberships:
          type: array
          items: { $ref: "#/components/schemas/Membership" }

    TeamDependency:
      type: object
      description: A dependency edge framed relative to the team you asked about.
      properties:
        object: { const: dependency }
        id: { type: string, format: uuid }
        counterpart_team: { $ref: "#/components/schemas/Ref" }
        direction:
          type: [string, "null"]
          description: Relative to the team you asked about. Null when no direction is recorded.
          enum: [we_depend_on_them, they_depend_on_us, mutual, null]
        modes:
          type: array
          description: >-
            Interaction-mode identifiers for this team's side of the edge.
            Standard modes are slugs; custom modes are UUIDs (no name-resolution
            endpoint in v1).
          items: { type: string }
        description: { type: [string, "null"] }

    GraphDependency:
      type: object
      description: >-
        A dependency edge in absolute terms (whole-graph view). The relationship
        is two-sided: each team may depend on the other, so both sides are given.
      properties:
        object: { const: dependency }
        id: { type: string, format: uuid }
        team_a: { $ref: "#/components/schemas/Ref" }
        team_b: { $ref: "#/components/schemas/Ref" }
        direction:
          type: [string, "null"]
          enum: [a_depends_on_b, b_depends_on_a, mutual, null]
        a_modes: { type: array, items: { type: string } }
        b_modes: { type: array, items: { type: string } }
        a_description: { type: [string, "null"] }
        b_description: { type: [string, "null"] }

    Error:
      type: object
      properties:
        error:
          type: object
          required: [type, message]
          properties:
            type: { type: string, example: invalid_token }
            message: { type: string }

  responses:
    Unauthorized:
      description: Missing, invalid, expired, or revoked token.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: No such resource in this organization.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    TooManyRequests:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          schema: { type: integer }
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
