# 조직 Context JSON API 계약

## 목적

외부 연동앱이 계정 세션 없이 M2M 방식으로 Baron SSO의 조직구성을 조회할 수 있게 한다. 조직구성은 Ory SSOT에서 웜업한 Redis cache와 Ory에 저장되지 않거나 조회가 불가능한 Backend read model을 Backend가 조합해 cursor 기반 API로 제공한다. Backend DB나 claim output을 SSOT로 사용하지 않으며, iframe 또는 `postMessage` 계약은 사용하지 않는다.

## 인증

API Key 기반 headless 통신만 허용한다.

```http
X-Baron-Key-ID: <client id>
X-Baron-Key-Secret: <client secret>
```

필요 scope는 다음과 같다.

```text
org-context:read
```

API Key 발급/회수는 사람의 관리 행위이므로 super admin 권한으로만 수행한다. 반면 아래 조직 Context 조회 API는 사용자 세션 없이 API Key만으로 동작한다.

## Endpoint

```http
GET /api/v1/integrations/org-context
```

### Query

| 이름 | 기본값 | 설명 |
| --- | --- | --- |
| `tenantSlug` | `hanmac-family` | 조회할 subtree root tenant slug. 지정하지 않으면 `hanmac-family` 전체 subtree를 반환한다. |
| `includeUsers` | `true` | `false`이면 각 tenant의 `members`를 빈 배열로 반환한다. |
| `includeUserIds` | `false` | `true`이면 각 tenant의 `members[].id`와 `members[].phone`만 추가한다. 사용자 UUID와 전화번호가 필요한 연동에서만 사용한다. |

상위 조직 지정은 slug만 사용한다. UUID 기반 지정은 계약에 포함하지 않는다.

## 예시 요청

```bash
curl 'https://sso.example.com/api/v1/integrations/org-context?tenantSlug=hanmac&includeUsers=true' \
  -H 'X-Baron-Key-ID: 01970f08-91da-7286-bd19-882fb98d1f2c' \
  -H 'X-Baron-Key-Secret: <secret>'
```

## 예시 응답

```json
{
  "schemaVersion": "baron.org-context.v1",
  "issuedAt": "2026-05-13T12:00:00Z",
  "scope": {
    "tenantId": "01970f08-91da-7286-bd19-882fb98d1f2c",
    "tenantSlug": "hanmac"
  },
  "tree": {
    "id": "01970f08-91da-7286-bd19-882fb98d1f2c",
    "type": "COMPANY",
    "name": "한맥기술",
    "slug": "hanmac",
    "parentId": "01970f07-4f01-7d9a-a71e-b53ad508f345",
    "status": "active",
    "description": "",
    "domains": [],
    "memberCount": 0,
    "visibility": "public",
    "createdAt": "2026-05-13T00:00:00Z",
    "updatedAt": "2026-05-13T00:00:00Z",
    "members": [],
    "children": [
      {
        "id": "01970f09-2b7b-7f83-b9d6-4f6c8b33f01a",
        "type": "USER_GROUP",
        "name": "플랫폼실",
        "slug": "platform",
        "parentId": "01970f08-91da-7286-bd19-882fb98d1f2c",
        "status": "active",
        "description": "",
        "domains": [],
        "memberCount": 0,
        "visibility": "internal",
        "orgUnitType": "실",
        "createdAt": "2026-05-13T00:00:00Z",
        "updatedAt": "2026-05-13T00:00:00Z",
        "members": [
          {
            "email": "user@example.com",
            "name": "홍길동",
            "grade": "책임",
            "position": "실장",
            "jobTitle": "Backend Engineer",
            "isOwner": true,
            "isLeader": true,
            "isPrimary": true
          }
        ],
        "children": []
      }
    ]
  },
  "tenants": [
    {
      "id": "01970f08-91da-7286-bd19-882fb98d1f2c",
      "type": "COMPANY",
      "name": "한맥기술",
      "slug": "hanmac",
      "parentId": "01970f07-4f01-7d9a-a71e-b53ad508f345",
      "status": "active",
      "description": "",
      "domains": [],
      "memberCount": 0,
      "visibility": "public",
      "createdAt": "2026-05-13T00:00:00Z",
      "updatedAt": "2026-05-13T00:00:00Z",
      "members": []
    },
    {
      "id": "01970f09-2b7b-7f83-b9d6-4f6c8b33f01a",
      "type": "USER_GROUP",
      "name": "플랫폼실",
      "slug": "platform",
      "parentId": "01970f08-91da-7286-bd19-882fb98d1f2c",
      "status": "active",
      "description": "",
      "domains": [],
      "memberCount": 0,
      "visibility": "internal",
      "orgUnitType": "실",
      "createdAt": "2026-05-13T00:00:00Z",
      "updatedAt": "2026-05-13T00:00:00Z",
      "members": [
        {
          "email": "user@example.com",
          "name": "홍길동",
          "grade": "책임",
          "position": "실장",
          "jobTitle": "Backend Engineer",
          "isOwner": true,
          "isLeader": true,
          "isPrimary": true
        }
      ]
    }
  ]
}
```

## 정책

- `tenantSlug`가 없으면 `hanmac-family` 전체 subtree를 반환한다.
- `tenantSlug`는 slug만 허용한다. UUID를 query 계약으로 쓰지 않는다.
- `visibility=private` tenant와 그 하위 tenant는 제외한다.
- `visibility=internal` tenant는 M2M 연동용 JSON API에는 포함한다.
- 외부 앱은 `schemaVersion`을 확인하고, 알 수 없는 version이면 별도 fallback을 적용한다.
- `tree`는 같은 tenant 집합을 계층 구조로 제공하고, `tenants`는 slug/id lookup용 flat array로 제공한다.
- 사용자 목록은 top-level `users`가 아니라 각 tenant의 `members`에 직접 소속 사용자 배열로 제공한다.
- `members`에는 `active`, `temporary_leave`, `suspended` 사용자만 포함한다. `preboarding`, `baron_guest`, `extended_leave`, `archived` 사용자는 email/local-part 선점 대상일 수 있지만 일반 조직도와 외부 연동 조직 조회에는 노출하지 않는다.
- tenant 세부 분류는 `type`과 `orgUnitType`으로 구분한다. `orgUnitType`은 tenant `config.orgUnitType` 값이 있을 때만 포함한다.
- 기본 사용자 응답은 로그인 claim 수준의 표시 정보만 제공한다. UUID, role/status, metadata, 생성/수정 시각은 기본 응답에 포함하지 않는다.
- 사용자 UUID와 전화번호가 필요한 연동은 `includeUserIds=true`를 사용한다. 이때 각 tenant `members[].id`와 `members[].phone`만 추가된다.
- `isOwner`는 appointment metadata의 `isOwner` 또는 `isManager` 기준이다.
- `isLeader`는 appointment metadata의 `lead` 또는 `isLead` 기준이며, `isOwner`/`isManager`가 true인 경우에도 true로 본다.
- `isPrimary`는 appointment metadata의 `representative`, `isPrimary`, `primary` 기준이다.
- appointment별 `grade`, `position`, `jobTitle`, `department`가 있으면 해당 tenant membership 값으로 우선 사용한다.
