
# Developer Guide

This guide is the starting point for developers integrating KOS into clinic websites, campaign pages, or external platforms. First decide whether to use **KOS Connect** or **KOS OpenAPI**, then follow the treatment menu and reservation tutorials.

Like Stripe-style integration docs, KOS integration is easiest when you follow a flow of `choose an integration path → quick start → server implementation → client implementation → testing and production checklist`. Do not start by reading every API page. Start from the user journey you are building, then read only the documents needed for that flow.

## Choose The Integration Path First

| What you want to build | Recommended path | Why |
| --- | --- | --- |
| Add a booking button or floating widget to a clinic website | KOS Connect | Use the ready-made booking screen, cart, and My Page provided by KOS. |
| Open a booking link from LINE, social media, or QR codes | KOS Connect | Provide the booking journey through a standalone website or LINE entry point. |
| Build your own treatment list, search, and detail pages | KOS OpenAPI | Read categories, products, options, prices, images, and translations directly. |
| Build your own reservation UI and server-side booking flow | KOS OpenAPI | Compose reservation groups, slots, visitor data, and reservation creation requests yourself. |
| Build your own menu UI but hand off reservation completion to KOS | OpenAPI + Connect | Use OpenAPI for product display and add selected options to the Connect cart. |

:::warning{title="appId and API Key are different"}
The KOS Connect `appId` identifies a widget or standalone Connect site. It is not an OpenAPI API Key or Auth Token. An OpenAPI API Key also does not automatically grant access to KOS Connect.
:::

## Full Implementation Flow

1. **Confirm access and identifiers**

   KOS Connect requires an `appId`. KOS OpenAPI requires an API Key and permissions issued to the clinic or partner development team.

2. **Separate server and client responsibilities**

   OpenAPI API Key handling and token exchange must happen on your server. Do not put an API Key in a browser, mobile webview, or static HTML. KOS Connect SDK runs in the browser, so do not call it during Next.js Server Component rendering or SSR.

3. **Display treatment data**

   Fetch categories and products, then apply `targetCountryCode`, `translsMap`, `displayPeriod`, and `offeringPeriod` when rendering the UI.

4. **Enter the reservation journey**

   With Connect, use commands such as `KOSConnect.open()`, `KOSConnect.changeTab()`, and `KOSConnect.addToCart()`. With OpenAPI, fetch reservation groups and slots, then call the reservation creation API.

5. **Check before production**

   Confirm that development and production appIds or API Keys are separated, clinic-specific display settings are ready, translations and target countries are correct, and product display periods are not confused with reservable periods.

## Common Server Pattern

When calling OpenAPI directly, keep token exchange, auth headers, retry, and response validation in a server-side API client.

```ts
const KOS_OPEN_API_BASE_URL = process.env.KOS_OPEN_API_BASE_URL!;
const KOS_OPEN_API_KEY = process.env.KOS_OPEN_API_KEY!;

let cachedToken: string | null = null;

async function getAuthToken(forceRefresh = false) {
  if (cachedToken && !forceRefresh) return cachedToken;

  const response = await fetch(
    `${KOS_OPEN_API_BASE_URL}/open/authorization/commands/exchange-token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ key: KOS_OPEN_API_KEY }),
    },
  );

  if (!response.ok) {
    throw new Error(`KOS token exchange failed: ${response.status}`);
  }

  const data = await response.json();
  cachedToken = data.token;
  return cachedToken;
}

export async function kosApiFetch(path: string, body: unknown) {
  let token = await getAuthToken();
  let response = await fetch(`${KOS_OPEN_API_BASE_URL}${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: token,
    },
    body: JSON.stringify(body),
  });

  if (response.status === 401) {
    token = await getAuthToken(true);
    response = await fetch(`${KOS_OPEN_API_BASE_URL}${path}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: token,
      },
      body: JSON.stringify(body),
    });
  }

  if (!response.ok) {
    throw new Error(`KOS API error: ${response.status} ${await response.text()}`);
  }

  return response.json();
}
```

:::tip{title="Add schema validation in production"}
The tutorial code is intentionally minimal. In production, add runtime schema validation, logging, timeouts, and error mapping.
:::

## Concepts That Often Cause Confusion

### `targetCountryCode` is not the UI language

`targetCountryCode` filters products by the country they target. It uses ISO 3166-1 alpha-2 codes, and `ETC` for other countries.

For example, an English UI can still show Korea-targeted products with `targetCountryCode: 'KR'`. A Korean UI can also show global products using `ETC` or another country code. Keep language and target country as separate decisions.

### `translsMap` overrides base fields

Products, options, and categories can provide translations for fields such as `title`, `description`, `caution`, and image URLs through `translsMap`. Always define fallback behavior.

```ts
function translated(
  translsMap: Record<string, { translation?: Record<string, string> }> | null | undefined,
  key: string,
  field: string,
  fallback: string,
) {
  if (!key) return fallback;
  return translsMap?.[field]?.translation?.[key] ?? fallback;
}
```

### `displayPeriod` and `offeringPeriod` are different

| Field | Meaning | Typical UI behavior |
| --- | --- | --- |
| `displayPeriod` | Product display period. If omitted, treat as always visible. | Decide whether to show the product in lists and detail pages. |
| `offeringPeriod` | Period when reservation and payment are allowed. If omitted, treat as always available. | Decide whether reservation, add-to-cart, or payment entry is enabled. |

If a product is outside its display period, hide it from the list or show an access notice. If the product is visible but outside its offering period, keep the content visible but disable reservation actions.

### Use `isEvent` for event or promotion products

Some promotion-specific APIs are deprecated. For new implementations, prefer fetching event products with `isEvent: true` from the product list API.

### Exchange times in UTC and localize only for display

Reservation slots and period fields use UTC ISO 8601 strings such as `startDateTimeUtc` and `endDateTimeUtc`. Send UTC to the API, then display times in the clinic or user-facing timezone.

## Reading Order

| Goal | Read first |
| --- | --- |
| Choose an integration path | This page |
| Build product list, detail, and search | [Build A Treatment Menu](/en/docs/guide/procedure-menu/start-procedure-menu) |
| Build custom slot and reservation creation flow | [Build Reservation Features](/en/docs/guide/reservation/start-reservation-tutorial) |
| Install the Connect widget | [Add A Widget To A Clinic Website](/en/docs/connect/getting-started/widget-integration) |
| Check Connect commands | [KOS Connect API](/en/docs/connect/api) |
| Check OpenAPI schemas | [OpenAPI Reference](/en/docs/openapi) |

## Production Checklist

- Did you avoid confusing Connect `appId` with OpenAPI `API Key`?
- Are OpenAPI API Keys and Auth Tokens never exposed to the browser?
- Are development and production base URLs, appIds, and API Keys separated?
- Did you confirm clinic-specific product display settings, category `opened`, product `deleted`, and `isEvent` filters?
- Did you keep language codes separate from `targetCountryCode`?
- Does your `translsMap` fallback work when translations are missing?
- Do `displayPeriod` and `offeringPeriod` create different UI states?
- Are reservation slot requests and reservation creation times UTC ISO 8601 strings?
- Do you handle capacity exceeded, overlapping reservation, and stale slot errors after slot selection?
- Did you avoid assuming operational policies, rate limits, SLAs, or production approval rules that are not documented?
