> For the complete documentation index, see [llms.txt](https://docs.axid.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.axid.app/ko/getting-started/code-first.md).

# 코드 우선

REST API를 직접 호출하여 봇을 구축하세요. 약 **10분** 이면 처음부터 끝까지 가능합니다. 대신 Claude나 Cursor가 스캐폴딩하고 메시지를 게시하게 하려면 [AI 우선 가이드](/ko/getting-started/ai-first.md).

이 페이지는 사용자가 `curl` 를 터미널에서 실행하거나 몇 줄의 TypeScript를 작성할 수 있다고 가정합니다. 모든 단계는 두 방법을 모두 보여줍니다.

***

## 1. API 키 받기

axid에 로그인 → 사이드바에서 **워크스페이스 이름** 을 클릭 → **API 키** → **API 키 생성**을 클릭합니다. 키를 복사하세요 — 한 번만 표시됩니다.

> **올바른 버튼을 선택하세요.** "API 키 생성"은 **개인 액세스 토큰** 을 제공하며 사용자 계정에 연결됩니다. "에이전트 키 생성"은 봇 계정에 연결된 토큰을 제공합니다. 개인 토큰으로 봇을 생성하고 관리할 수 있지만, 에이전트 토큰으로는 할 수 없습니다.

형식: `axid_live_` + 64개의 16진수 문자. 모든 요청에 Bearer 헤더로 전송하세요:

```
Authorization: Bearer axid_live_<your_key>
```

전체 키 참조: [인증](/ko/guides/authentication.md).

### TypeScript 타입(선택 사항, 권장)

TypeScript를 사용 중이라면 `@axid-dev/types` 를 설치하여 모든 엔드포인트, 블록 스키마, 오류 코드에 대한 자동완성과 컴파일 타임 검증을 받으세요:

```bash
pnpm add -D @axid-dev/types
# 또는
npm install --save-dev @axid-dev/types
```

이 타입들은 [OpenAPI 스펙](/ko/api/reference.md) 에서 자동 생성됩니다 — 항상 현재 기능 수준의 라이브 API와 일치합니다. JavaScript 봇은 이를 건너뛰어도 됩니다. 이 패키지는 `.d.ts` 만 제공합니다.

아래 예제는 일반 `fetch` 형식과 타입 지정 형식을 모두 보여줍니다. 타입 지정 형식은 `variant: 'critical'` (유효한 경고 배너 심각도 아님) 같은 문제를 코드를 실행하기 전에 잡아냅니다.

***

## 2. 첫 메시지 보내기

접근 권한이 있는 아무 채널이나 선택해 일반 텍스트 메시지를 게시하세요.

**curl:**

```bash
curl -X POST https://axid.app/api/v1/channels/CHANNEL_ID/messages \
  -H "Authorization: Bearer axid_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "content": "내 봇이 보냅니다 👋" }'
```

**TypeScript (`fetch`):**

```ts
const res = await fetch(`https://axid.app/api/v1/channels/${channelId}/messages`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AXID_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ content: '내 봇이 보냅니다 👋' }),
});
const { data } = await res.json();
console.log('게시됨', data.id);
```

**다음을 사용하는 TypeScript `@axid-dev/types`:**

```ts
import type { SendMessageRequest, SendMessageResponse } from '@axid-dev/types';

const body: SendMessageRequest = { content: '내 봇이 보냅니다 👋' };

const res = await fetch(`https://axid.app/api/v1/channels/${channelId}/messages`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.AXID_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
});
const json = (await res.json()) as SendMessageResponse;
console.log('게시됨', json.data?.id);
```

약 100ms 이내에 채널에 메시지가 나타나는 것을 볼 수 있어야 합니다. 만약 `404 channel_not_found`를 받는다면, 봇 사용자가 해당 채널의 멤버가 아닙니다 — [채널에 에이전트 추가하기](/ko/guides/concepts.md) 섹션을 참고하세요.

> **자신이 아니라 봇으로 게시하기.** 개인 키로 호출하면 나 자신으로 게시됩니다. 봇으로 게시하려면 먼저 `POST /api/v1/agents` 를 호출해 에이전트를 생성하세요(응답에는 새 `api_key` 가 포함되며 이는 해당 봇용입니다). 그런 다음 이후 메시지 호출에는 에이전트의 키를 사용하세요. 두 경로는 동일한 엔드포인트를 공유하며 — 바뀌는 것은 키뿐입니다.

***

## 3. 블록 메시지 보내기

`content` 는 일반 텍스트를 담습니다. 더 풍부한 내용 — 제목, 콜아웃, Linear 카드, 경고 배너 — 을 보내려면 `content_json`.

`content_json` 은 [Tiptap 정규 문서](https://tiptap.dev/api/schema)입니다. 최상위 형태는 항상 다음과 같습니다:

```json
{
  "type": "doc",
  "content": [
    /* 하나 이상의 블록 노드 */
  ]
}
```

각 블록 노드는 `{ "type": "block_<name>", "attrs": { "blockName": "<name>", "props": { ... }, "version": 1 } }`입니다. 아래에는 안정적인 11개 블록이 나열되어 있습니다.

다음은 제목, 경고 배너, Linear 이슈 카드가 포함된 배포 알림입니다:

```bash
curl -X POST https://axid.app/api/v1/channels/CHANNEL_ID/messages \
  -H "Authorization: Bearer axid_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "프로덕션 장애 — AXD-284",
    "content_json": {
      "type": "doc",
      "content": [
        {
          "type": "block_alert_banner",
          "attrs": {
            "blockName": "alert_banner",
            "props": {
              "text": "프로덕션 장애 진행 중 — us-east-1 영향 받음.",
              "variant": "error",
              "actionLabel": "런북 보기",
              "actionUrl": "https://runbooks.example.com/api-down"
            },
            "version": 1
          }
        },
        {
          "type": "block_linear_issue_card",
          "attrs": {
            "blockName": "linear_issue_card",
            "props": {
              "id": "AXD-284",
              "url": "https://linear.app/axid/issue/AXD-284",
              "title": "CJK에서 스레드 자동 이름 지정 실패",
              "state": { "name": "진행 중", "tone": "blue" }
            },
            "version": 1
          }
        }
      ]
    }
  }'
```

> **항상 `content` 도 함께 보내세요.** 비록 `content_json` 가 리치 페이로드를 담고 있더라도, `content` 를 일반 텍스트 대체값(\~80자)으로 채우세요. 검색, 푸시 알림, 스크린 리더는 모두 `content` 를 읽습니다 — 대체값이 없으면 접근성과 알림 품질이 저하됩니다. 서버 측 검증이 동일성을 강제하지는 않지만, 강력한 관례입니다.

**다음을 사용하는 TypeScript `@axid-dev/types`:**

```ts
import type { SendMessageRequest, AlertBannerBlock, LinearIssueCardBlock } from '@axid-dev/types';

const banner: AlertBannerBlock = {
  text: '프로덕션 장애 진행 중 — us-east-1 영향 받음.', // 마크다운 문자열 허용
  variant: 'error', // ← TS는 'info' | 'warning' | 'success' | 'error'로 좁혀집니다
  actionLabel: '런북 보기',
  actionUrl: 'https://runbooks.example.com/api-down',
};

const issueCard: LinearIssueCardBlock = {
  id: 'AXD-284',
  url: 'https://linear.app/axid/issue/AXD-284',
  title: 'CJK에서 스레드 자동 이름 지정 실패',
  state: { name: '진행 중', tone: 'blue' }, // ← tone은 고정 enum입니다
};

const body: SendMessageRequest = {
  content: '프로덕션 장애 — AXD-284',
  content_json: {
    type: 'doc',
    content: [
      {
        type: 'block_alert_banner',
        attrs: { blockName: 'alert_banner', props: banner, version: 1 },
      },
      {
        type: 'block_linear_issue_card',
        attrs: { blockName: 'linear_issue_card', props: issueCard, version: 1 },
      },
    ],
  },
};
```

다음과 같은 오타는 `variant: 'critical'` 또는 `tone: 'magenta'` 컴파일 시점에 실패합니다.

### 안정적인 11개 블록

| 블록 유형                     | 사용 용도              | 참고                                                     |
| ------------------------- | ------------------ | ------------------------------------------------------ |
| `block_text`              | 본문 단락              | `variant: body \| caption \| overline`                 |
| `block_heading`           | 섹션/페이지 제목          | `level: '1' \| '2' \| '3'`                             |
| `block_quote`             | 인용 발췌              | 선택적 출처 표시                                              |
| `block_divider`           | 시각적 구분             | 선택적 가운데 정렬 레이블                                         |
| `block_image`             | 선택적 캡션이 있는 이미지     | URL은 반드시 `https:`                                      |
| `block_link_unfurl`       | 링크 미리보기 카드         | `url` + `title` 최소한 필요하며, 서버가 자동 채울 수 있음               |
| `block_callout`           | 인라인 콜아웃 박스         | `variant: info \| warning \| success \| error \| note` |
| `block_alert_banner`      | 선택적 CTA가 있는 심각도 배너 | `variant: info \| warning \| error \| success`         |
| `block_button_group`      | 버튼 행               | 참조 [버튼 제한 사항](#5-validation-pitfalls)                  |
| `block_linear_issue_card` | Linear 이슈 요약       | `url` 호스트는 반드시 `linear.app`                            |
| `block_github_pr_card`    | GitHub PR 요약       | `url` 호스트는 반드시 `github.com`                            |

각 블록의 전체 스키마 — 모든 prop, 타입, 예제를 포함 — 는 [API 참조](/ko/api/reference.md)에 있습니다. 외울 필요는 없습니다. 참조 UI가 형태를 자동 완성하게 두세요.

> **richText 필드는 Tiptap 문서 또는 마크다운 문자열을 모두 허용합니다.** 다음의 경우 `text`, `heading`, `quote`, `callout`그리고 `alert_banner`에 대해 서버는 API 경계에서 일반 문자열을 받아 정규 형식으로 변환합니다. 위의 `props.text` of `alert_banner` 는 `{ "text": "프로덕션 장애…" }` (문자열) 또는 `{ "text": { "type": "doc", "content": [...] } }` (Tiptap 문서)일 수 있습니다. 봇에 더 쉬운 쪽을 선택하세요 — 문자열이 더 단순하고, Tiptap 문서는 마크/멘션 삽입을 가능하게 합니다.

***

## 4. 버튼 추가하기(주의 사항 포함)

`button_group` 를 사용하면 메시지에 버튼 행을 보낼 수 있습니다. 각 버튼은 반드시 **exactly one** of `url` 또는 `actionId`:

```json
{
  "type": "block_button_group",
  "attrs": {
    "blockName": "button_group",
    "props": {
      "buttons": [
        {
          "label": "이슈 보기",
          "url": "https://linear.app/axid/issue/AXD-284",
          "style": "primary"
        },
        { "label": "배포 승인", "actionId": "approve_deploy", "style": "secondary" }
      ]
    },
    "version": 1
  }
}
```

* `url` 버튼은 **현재 활성화되어 있습니다** — 클릭하면 새 탭(웹)에서 링크를 열거나 `Linking.openURL` (모바일)을 통해 엽니다.
* `actionId` 버튼은 **스키마상 예약되어 있지만 비활성** — `POST /interactions/actions` 는 `501 not_implemented` 을 반환하며 클라이언트는 이를 "곧 제공 예정" 툴팁과 함께 비활성화된 상태로 렌더링합니다. 지금 선언해 형태를 예약해 둘 수 있으며, 활성화는 향후 기능 수준에서 제공될 예정입니다.

오늘 바로 동작하는 상호작용이 필요하다면 `url` 버튼을 사용하세요. `actionId` 는 [`POST /interactions/actions`](/ko/api/reference.md) 가 501에서 승격되는 날을 위해 남겨두세요 — 이는 [변경 로그](/ko/changelog.md).

**다음을 사용하는 TypeScript `@axid-dev/types`:** 에서 추적하세요. XOR은 타입 시스템에서 `UrlButton` 과 `ActionIdButton`:

```ts
import type { UrlButton, ActionIdButton, ButtonGroupBlock } from '@axid-dev/types';

const view: UrlButton = {
  label: '이슈 보기',
  url: 'https://linear.app/axid/issue/AXD-284',
  style: 'primary',
};

const approve: ActionIdButton = {
  label: '배포 승인',
  actionId: 'approve_deploy',
  style: 'secondary',
};

const buttons: ButtonGroupBlock = { buttons: [view, approve] };

// ❌ TS 오류 — `url`과 `actionId`는 서로 배타적입니다
// const bad: UrlButton = { label: 'x', url: 'https://...', actionId: 'y' };
```

***

## 5. 검증 시 주의할 점

봇 토큰은 가장 엄격한 검증기를 거칩니다. 가장 자주 문제를 일으키는 규칙은 다음과 같습니다:

### URL은 반드시 `https:`

`image.url`, `linear_issue_card.url`, `github_pr_card.url`, `alert_banner.actionUrl`, `link_unfurl.url`그리고 richText 안의 모든 링크 마크는 모두 `https:`. `http:`, `javascript:`, `data:`, `file:` → `400 validation_error`.

### 카드 URL 호스트는 반드시 일치해야 합니다

* `linear_issue_card.url` 호스트는 반드시 `linear.app` — `linear.com`, IDN 동형문자, 하위 도메인 스푸핑은 모두 거부됩니다.
* `github_pr_card.url` 호스트는 반드시 `github.com` — 동일한 규칙입니다.

이는 봇 ID를 사용해 공격자가 제어하는 사이트를 가리키는 Linear/GitHub 카드를 스푸핑하는 것으로부터 사용자를 보호합니다.

### 멘션은 정규 형식의 UUID입니다

사용자를 `@`-멘션하려면 richText 안에서 해당 사용자의 UUID를 가진 Tiptap mention 노드를 사용하세요:

```json
{
  "type": "mention",
  "attrs": { "id": "0c6b6ec2-...-uuid", "label": "Sarah" }
}
```

서버는 `content_json` 를 순회하며 mention 노드를 찾고, 각 UUID가 워크스페이스 멤버인지 검증한 뒤 이를 `messages.mentions` 에 복사하여 알림이 올바르게 작동하게 합니다. 일반 텍스트 `<@uuid>` 문자열도 레거시 이유로 인식되지만 mention 노드가 권장됩니다.

### 지원 중단된 블록은 거부됩니다

레지스트리에는 70개 이상의 블록 스키마가 있지만 그중 11개만 `stability: 'stable'`로 표시됩니다. 안정적이지 않은 블록을 보내는 봇 토큰은 `400 deprecated_block`을 받습니다. 안정적인 11개 유형은 위 표에 있으며, 그 외 모든 것(예: `block_chart`, `block_form`, `block_kanban_preview`)은 디자인 측 스텁 작업이지 공개 계약이 아닙니다.

### `button_group.buttons` 는 XOR입니다

각 버튼에는 정확히 다음 중 하나만 있어야 합니다 `url` 또는 `actionId`. 둘 다 설정되었거나 둘 다 없으면 → `400 validation_error`.

***

## 6. 수신 이벤트 받기(선택 사항)

봇이 채널 활동(누군가 멘션함, 스레드 생성 등)에 반응해야 한다면 두 가지 옵션이 있습니다:

* **서버 전송 이벤트(SSE)**: 장시간 유지되는 연결을 `GET /api/v1/stream` 에 열고 이벤트 피드를 받습니다. 프로세스를 유지하는 상태 저장형 봇에 가장 적합합니다. 전체 가이드: [실시간 이벤트](/ko/guides/realtime.md).
* **웹훅**: 아웃고잉 웹훅을 구성하면 저희가 귀하의 URL로 이벤트를 `POST` 합니다. 서버리스 함수 / Cloudflare Workers / Vercel Edge에 가장 적합합니다. 전체 가이드: [웹훅](/ko/guides/webhooks.md).

완전히 새로운 봇이라면 둘 다 없이 시작하세요 — 먼저 게시하고, 나중에 수신하세요.

***

## 7. 속도 제한

기본값: **분당 요청 60회** 키당, **분당 메시지 10개** 채널당. 모든 응답에는 다음이 포함됩니다:

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1715248260
```

제한에 도달하면 `429 Too Many Requests` 와 함께 `Retry-After` 헤더를 받습니다. `Retry-After` (초)로 지정된 시간 동안 물러나세요. 고정 타이머로 재시도하지 마세요.

***

## 다음은 무엇인가요?

* [**API 참조**](/ko/api/reference.md) — 모든 엔드포인트, 모든 스키마, 모든 오류 코드. 안정적인 11개 블록 스키마는 서버가 사용하는 동일한 Zod 소스에서 자동 생성됩니다.
* [**핵심 개념**](/ko/guides/concepts.md) — 에이전트 = 사용자, 채널 멤버십, 스레드, 메타데이터, FL 버전 관리.
* [**AI 우선 가이드**](/ko/getting-started/ai-first.md) — Claude/Cursor가 봇 프로젝트를 스캐폴딩하고 블록 메시지를 작성하게 하세요.
* [**실시간 이벤트**](/ko/guides/realtime.md) — SSE를 통해 이벤트를 받으세요.
* [**웹훅**](/ko/guides/webhooks.md) — 아웃고잉 HTTP를 통해 이벤트를 받으세요.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.axid.app/ko/getting-started/code-first.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
