> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# Decisions API

> 애플리케이션 상태, 선택적 이미지, 타입이 지정된 질문을 보내고 확률이 포함된 타입별 답변을 받으세요. 이미지 좌표도 지원합니다. 동작을 실행하기 전에 응답을 검증하세요.

# Decisions API

대부분의 모델 호출은 문단을 반환하고, 결정을 추출하는 작업은 애플리케이션에 맡깁니다. JSON을 요청하고 파싱하고 검증한 뒤 형식이 어긋나면 다시 시도해야 합니다. Decisions API는 이 단계를 없앱니다. 애플리케이션의 현재 상태와 질문을 보내면 답변은 이미 정해진 타입으로 돌아옵니다. 예일 확률, 직접 이름을 붙인 선택지 중 하나, 직접 정의한 척도의 위치를 확률과 함께 받을 수 있습니다.

대화보다는 소프트웨어 내부의 판단에 적합합니다. 문의를 분류하거나, 도구 호출을 허용하거나, 대기열의 우선순위를 정하거나, 사람이 검토하기 전에 제출물을 평가할 때 사용합니다.

`POST https://api.beatapi.io/v1/systemone`

엔드포인트 경로와 요청·응답 본문은 TypeSafe의 형식을 그대로 따릅니다. 기존 TypeSafe 클라이언트나 SDK에서는 기본 URL과 모델 이름만 바꾸면 됩니다. `POST /v1/decisions`도 별칭으로 유지됩니다.

| 모델         | 선택 기준                                                                             |
| ---------- | --------------------------------------------------------------------------------- |
| `jev-1.14` | **여기서 시작하세요.** 입력 토큰 100만 개당 \$0.0441입니다. 출력은 과금하지 않습니다. 이미지와 `box` 질문을 지원합니다.    |
| `jev-1.13` | 기존 연동을 위해 유지하는 이름입니다. 가격은 `jev-1.14`와 같고 실제 1.13 버전이 응답합니다. 이미지와 `box` 질문을 지원합니다. |

`jev-1.14`는 TypeSafe의 현행 버전 `jev-latest`를 따르며, 현재는 `jev-1.14.0`으로 연결됩니다. 1.13보다 전반적으로 조금씩 나아졌고 다국어 입력에서 가장 크게 개선되었습니다. TypeSafe가 수치를 공개하지 않아 여기에도 적지 않습니다. `jev-1.13`은 실제 1.13 버전이 응답하고, 기존 연동이 계속 작동하도록 그대로 받으며 가격도 같습니다.

> **Note**
>
> 이 엔드포인트는 `/v1/chat/completions`가 아니며, 해당 경로에서는 JEV 모델을 사용할 수 없습니다. 요청에 `messages`가 없고 응답에 `choices`가 없습니다. 결정 모델은 텍스트를 생성하지 않습니다.

## 빠른 시작

잔액이 있는 BeatAPI 키로 `jev-1.14`를 호출하세요. 입력은 **100만 토큰당 \$0.0441**, 출력은 무료이며 호출은 계정의 일반 요청 한도에 포함됩니다. 무료 버전 `jev-1.14-free`와 `jev-1.13-free`는 2026-11-05에 종료되었습니다. [요청 한도](#request-limits)를 참고하세요.

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.14",
    "state": "Task: clean up inactive accounts before the quarterly report.\nProposed tool call: delete_rows(table=\"customers\", where=\"last_login < 2023-01-01\")\nContext: the customers table has 48,210 rows and no backup was taken today.",
    "questions": {
      "safe_to_run": {
        "type": "noul",
        "instructions": "Is this action safe to run without a human approving it first?",
        "criteria": {
          "true": "Reversible or low-impact, and clearly within the stated task.",
          "false": "Destructive, irreversible, or broader than the task requires."
        }
      }
    }
  }'
```

다음 응답은 설명용 예시이며 측정 결과가 아닙니다. JSON을 파싱하고 HTTP 상태와 답변 타입을 확인한 뒤, 값을 검증하고 자체 임계값을 적용하세요.

```json
{
  "id": "task_01J...",
  "model": "jev-1.14",
  "answers": {
    "safe_to_run": { "type": "noul", "noul": 0.04 }
  },
  "usage": { "input_tokens": 384, "output_tokens": 22 }
}
```

`0.04`는 모델이 추정한 *예*의 확률, 즉 4%입니다. 따라서 에이전트는 멈추고 사람에게 확인합니다. 이것이 이 질문을 하는 목적입니다.

### JSON 복구 반복 없이 타입이 정해진 결정 받기

BeatAPI를 대상으로 지정하고 상태와 질문을 보낸 뒤 답변을 읽으세요. 별칭 `/v1/decisions`도 동일하게 동작합니다. chat/completions가 아니며 해당 경로에서는 JEV 모델을 사용할 수 없습니다.

```javascript
const response = await fetch("https://api.beatapi.io/v1/systemone", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.BEATAPI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "jev-1.14",
    state: {
      task: "clean up inactive accounts",
      tool: 'delete_rows(table="customers", where="last_login < 2023-01-01")',
      rows: 48210,
      backup_today: false,
    },
    questions: {
      safe_to_run: {
        type: "noul",
        instructions: "Is this action safe to run without a human approving it first?",
        criteria: {
          true: "Reversible or low-impact, and clearly within the stated task.",
          false: "Destructive, irreversible, or broader than the task requires.",
        },
      },
    },
  }),
});

if (!response.ok) {
  // On HTTP 429, wait the Retry-After seconds before retrying.
  throw new Error(`JEV HTTP ${response.status}; Retry-After: ${response.headers.get("Retry-After")}`);
}
const decision = await response.json();
const answer = decision.answers?.safe_to_run;
if (answer?.type !== "noul" || !Number.isFinite(answer.noul)
    || answer.noul < 0 || answer.noul > 1) {
  throw new Error("Invalid JEV answer; route to human review");
}
// Illustrative threshold: evaluate it on your own cases before use.
const needsReview = answer.noul < 0.9;
console.log({ probability: answer.noul, needsReview });
```

## 요청

| 필드          | 유형                        |  필수 | 설명                                                                                                                                                        |
| ----------- | ------------------------- | --: | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model`     | string                    |   예 | `jev-1.14`. `jev-1.13`도 계속 사용할 수 있습니다. `jev-1.14-free`와 `jev-1.13-free`는 2026-11-05에 종료되어 더 이상 사용할 수 없습니다. 모델이 없는 요청은 거부됩니다.                              |
| `state`     | string \| object \| array |   예 | 애플리케이션이 현재 알고 있는 정보입니다. 일반 문자열, JSON 객체, 텍스트 값 배열을 사용할 수 있습니다. 이미지는 `assets`에서 정의한 뒤 텍스트에서 참조하세요([이미지](#images) 참고). 오디오, 동영상, 기타 바이너리는 먼저 텍스트로 변환해야 합니다. |
| `questions` | object                    |   예 | 이름이 지정된 질문 하나 이상입니다. 직접 정한 이름이 답변의 키가 됩니다.                                                                                                                |
| `assets`    | object                    | 아니요 | 텍스트에서 참조하는 이미지입니다. `"<door>"`처럼 직접 정한 참조 문자열을 키로 사용합니다. `box` 질문에는 필수입니다. [이미지](#images)를 참고하세요.                                                          |

각 질문에는 `type`, `instructions`, 그리고 타입에 따라 `criteria`가 들어갑니다.

| 필드             | 유형                        |                필수 | 설명                                                                                                                           |
| -------------- | ------------------------- | ----------------: | ---------------------------------------------------------------------------------------------------------------------------- |
| `type`         | string                    |                 예 | `noul`, `choice`, `score`, `box` 중 하나입니다.                                                                                    |
| `instructions` | string \| object \| array | `noul`과 `box`에 필수 | 동료에게 묻듯 자연스럽게 작성한 질문입니다. `noul`에서는 예/아니요의 대상, `box`에서는 찾을 대상을 지정하므로 필수입니다. `choice`와 `score`는 `criteria`만으로 질문을 정의할 수도 있습니다. |
| `criteria`     | 아래 참고                     |         타입에 따라 다름 | 답변 값의 의미입니다. `box`에서는 사용할 수 없습니다.                                                                                            |

## 네 가지 질문 타입

### `noul`: 보정된 확률

임의의 선택이 아닌 임계값을 기준으로 예/아니요를 판단할 때 사용합니다. 답변은 `0`부터 `1` 사이의 숫자입니다.

```json
"refund_ok": {
  "type": "noul",
  "instructions": "Should this refund be approved automatically?",
  "criteria": {
    "true": "Clear billing error, under $100, first request from this account.",
    "false": "Disputed usage, over $100, or a repeat request."
  }
}
```

```json
"refund_ok": { "type": "noul", "noul": 0.83 }
```

여기서 `criteria`는 선택 사항이지만 양쪽 기준을 설명하면 답변이 더 명확해집니다. 임계값은 직접 정하세요. 예를 들어 `0.9` 초과는 자동 승인, `0.6` 미만은 상위 담당자에게 전달하고, 중간 값은 검토 대기열에 넣을 수 있습니다.

> **Note**
>
> `noul`은 실제 타입 이름이며 `bool`의 오타가 아닙니다. 타입 이름을 잘못 쓰면 유효한 타입 목록과 함께 400 오류가 반환됩니다.

### `choice`: 이름을 붙인 선택지 중 하나

`criteria`는 각 선택지 이름과 의미를 연결하는 객체입니다. 답변에는 선택된 항목과 전체 확률 분포가 포함됩니다.

```json
"route": {
  "type": "choice",
  "instructions": "Which queue should this ticket go to?",
  "criteria": {
    "billing": "Payment, invoices, refunds, or charges.",
    "technical": "The product is broken or erroring.",
    "sales": "Pre-purchase questions about plans or pricing."
  }
}
```

```json
"route": {
  "type": "choice",
  "choice": "billing",
  "probabilities": { "billing": 1, "technical": 0, "sales": 0 },
  "confidence": 1
}
```

### `score`: 직접 정의한 척도의 위치

`criteria`는 낮은 값부터 척도를 설명하는 배열입니다. 답변은 `0`부터 시작하는 **배열 인덱스** 위의 연속값입니다.

```json
"urgency": {
  "type": "score",
  "instructions": "How urgent is this ticket?",
  "criteria": [
    "1 - can wait a week",
    "2 - answer within a few days",
    "3 - answer today",
    "4 - answer within an hour",
    "5 - wake someone up"
  ]
}
```

```json
"urgency": {
  "type": "score",
  "score": 1.98,
  "legend": { "0": "1 - can wait a week", "1": "2 - answer within a few days",
              "2": "3 - answer today", "3": "4 - answer within an hour",
              "4": "5 - wake someone up" },
  "probabilities": { "0": 0, "1": 0.17, "2": 0.67, "3": 0.16, "4": 0 },
  "confidence": 0.72
}
```

`1.98`은 `legend["1"]`과 `legend["2"]` 사이에서 후자인 '오늘 답변'에 매우 가깝습니다. 범주가 필요하면 반올림하고, 대기열을 정렬하려면 소수 부분을 유지하세요.

### `box`: 이미지에서 대상의 위치 찾기

이미지의 대상 하나를 찾을 때 사용합니다. `box`는 `jev-1.14`와 `jev-1.13` 모두 지원합니다. `instructions`에 찾을 대상을 쓰고 `criteria`는 넣지 않습니다. 이미지가 하나 이상 있어야 하며([이미지](#images) 참고), 없으면 400 오류가 반환됩니다.

```json
"where": {
  "type": "box",
  "instructions": "The red rectangle in <picture>."
}
```

```json
"where": {
  "type": "box",
  "box": { "top": 0.12, "bottom": 0.43, "left": 0.56, "right": 0.89 },
  "confidence": 0.93
}
```

네 값은 위쪽과 왼쪽 가장자리를 기준으로 한 이미지 높이(`top`, `bottom`)와 너비(`left`, `right`)의 비율로, 각각 `0`부터 `1` 사이입니다. 이미지의 픽셀 크기를 곱하면 픽셀 좌표가 됩니다. `confidence`는 `null`일 수 있으므로 임계값과 비교하기 전에 확인하세요.

## 이미지

1.13과 1.14 모델 모두 이미지를 읽습니다. 최상위 `assets` 객체에 이미지를 한 번 정의하고, `state`의 텍스트, 질문의 `instructions`, 또는 `criteria` 값에서 참조 문자열을 사용하세요. 참조가 나타나는 위치에 이미지가 배치됩니다.

| 필드            | 유형     |  필수 | 설명                                       |
| ------------- | ------ | --: | ---------------------------------------- |
| `type`        | string | 아니요 | `image`이며 생략할 수 있습니다.                    |
| `format`      | string |   예 | `png`, `jpg`, `jpeg` 중 하나입니다.            |
| `data_base64` | string |   예 | `data:` 접두사 없는 표준 base64 형식의 이미지 바이트입니다. |

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.14",
    "state": "Check the uploaded image.",
    "assets": {
      "<picture>": { "type": "image", "format": "png", "data_base64": "iVBORw0KGgo..." }
    },
    "questions": {
      "red":   { "type": "noul", "instructions": "Is there a red rectangle in <picture>?" },
      "where": { "type": "box",  "instructions": "The red rectangle in <picture>." }
    }
  }'
```

이 호출에는 빨간 사각형 하나가 있는 280×280 픽셀의 흰색 이미지를 사용했습니다. 아래 응답은 실제 측정 결과입니다. `id`는 공급자 필드에 추가되는 BeatAPI 요청 식별자입니다.

```json
{
  "id": "task_01J...",
  "model": "jev-1.14",
  "answers": {
    "red":   { "type": "noul", "noul": 0.92 },
    "where": { "type": "box",
               "box": { "top": 0.12, "bottom": 0.43, "left": 0.56, "right": 0.89 },
               "confidence": 0.93 }
  },
  "usage": { "input_tokens": 501, "output_tokens": 36 }
}
```

참조 동작 방식:

* 참조는 일반 텍스트로 일치시킵니다. `"<door>"`나 `"[img-1]"`처럼 원하는 문자열을 사용할 수 있습니다. 객체 키는 검색하지 않으므로 참조를 값에 넣으세요.
* 정의한 모든 참조를 한 번 이상 사용해야 합니다. 사용하지 않은 참조가 있으면 400 오류가 반환됩니다.
* JSON만 보내세요. Multipart 업로드는 지원하지 않습니다. `part` 필드가 있는 리소스는 `data_base64`를 사용하라는 400 오류로 거부됩니다.
* TypeSafe API에서 요구하는 `TypeSafe-Danger-Zone-Images` 헤더는 보내지 않아도 됩니다. BeatAPI가 추가합니다. 직접 보내도 문제없습니다.

이미지 제한:

* 정적 PNG 또는 JPEG만 지원하며 애니메이션 이미지는 거부됩니다.
* 디코딩 후 이미지당 최대 16 MiB, 전체 요청 본문은 최대 32 MiB입니다.
* 각 변은 280\~1568픽셀이어야 합니다. 최솟값보다 작으면 `Invalid image dimensions.` 오류가 반환됩니다.
* 이미지 크기를 자동으로 조정하지 않습니다. 한도보다 크면 전송 전에 줄이세요.
* 이미지 하나라도 잘못되면 전체 요청이 실패합니다.

## 한 번의 호출로 여러 질문하기

같은 상태에 관한 질문을 한 번에 보내세요. 각 답변은 독립적이며 공통 상태는 질문마다가 아니라 한 번만 계산됩니다.

```bash
curl https://api.beatapi.io/v1/systemone \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "jev-1.14",
    "state": {"ticket": "charged twice this month", "plan": "pro", "tenure_days": 412},
    "questions": {
      "refund_ok": {"type": "noul", "instructions": "Approve the refund automatically?"},
      "urgency":   {"type": "score", "instructions": "How urgent?", "criteria": ["low", "medium", "high"]}
    }
  }'
```

## 응답

| 필드        | 설명                                                                               |
| --------- | -------------------------------------------------------------------------------- |
| `model`   | 답변한 모델입니다.                                                                       |
| `answers` | 질문마다 지정한 이름을 키로 하는 항목입니다. 각 항목에 `type`과 아래 필드가 포함됩니다.                            |
| `usage`   | 이번 호출의 `input_tokens`와 `output_tokens`입니다.                                       |
| `id`      | 지원 문의에 사용할 BeatAPI 요청 식별자입니다. 공급자의 세 필드에 추가됩니다. 이 필드를 정의하지 않은 클라이언트는 무시할 수 있습니다. |

질문 타입별 답변 필드:

| `type`   | 필드                                                                                                                   |
| -------- | -------------------------------------------------------------------------------------------------------------------- |
| `noul`   | `noul`: *예*의 확률로, `0`부터 `1` 사이입니다.                                                                                   |
| `choice` | `choice`: 선택된 항목, `probabilities`: 항목별 확률, `confidence`.                                                             |
| `score`  | `score`: `criteria` 인덱스 위의 값, `legend`, `probabilities`, `confidence`.                                               |
| `box`    | `box`: `top`, `bottom`, `left`, `right`. 위쪽과 왼쪽 가장자리를 기준으로 한 높이 또는 너비의 `0`\~`1` 비율입니다. `confidence`는 `null`일 수 있습니다. |

동기 응답이므로 HTTP 응답이 성공하면 결정이 완료된 것입니다. 상태를 폴링하거나 결과 파일을 가져올 필요가 없습니다.

## 제한

* **컨텍스트:** `state`와 모든 질문을 합쳐 64K 토큰입니다. `state`와 가장 긴 질문 하나의 합은 32K 토큰 이하여야 합니다. [모델 제한](https://docs.typesafe.ai/models)을 참고하세요.
* **확장 컨텍스트는 아직 지원하지 않습니다.** TypeSafe는 262K 토큰으로 늘리는 헤더를 제공하지만 BeatAPI는 지원하지 않습니다. 이미지 토큰을 포함해 위의 64K 한도가 적용됩니다.
* **이미지:** Multipart 없이 JSON만 사용하며 [이미지](#images)의 제한을 따릅니다.
* **샘플링 매개변수 없음:** `temperature`, `top_p`, `seed` 등은 사용할 수 없습니다. 답변 형식은 결정적으로 설계되었습니다.
* **스트리밍 없음:** 답변은 토큰 시퀀스가 아닌 값입니다.
* **`choice`에는 비어 있지 않은 `criteria` 객체, `score`에는 비어 있지 않은 `criteria` 배열이 필요합니다.** 없으면 원인을 설명하는 400 오류가 반환됩니다. `instructions`는 선택 사항이지만 제공할 경우 비어 있으면 안 됩니다.

## 오류

오류는 표준 BeatAPI 오류 형식을 사용하며 항상 `retryable`을 포함합니다. 전체 표와 재시도 정책은 [오류](/errors)를 참고하세요.

| HTTP | 코드                                               | 의미                                                                                                                                     |
| ---: | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|  400 | `bad_request`                                    | 잘못된 JSON, 모델, 질문 타입, 기준 또는 이미지입니다. 메시지에 필드가 표시됩니다. 사용하지 않거나 잘못된 `assets`, `part` 필드, 이미지 없는 `box`, 지원하지 않는 이미지 또는 범위를 벗어난 크기 등이 해당합니다. |
|  401 | `missing_api_key` / `invalid_api_key`            | 키가 없거나 잘못되었거나, 만료·비활성화되었거나 키 할당량을 모두 사용했습니다.                                                                                           |
|  402 | `insufficient_credits`                           | 잔액이 0이거나 호출 비용보다 부족합니다.                                                                                                                |
|  403 | `forbidden`                                      | 키나 계정에 해당 모델의 사용 권한이 없습니다.                                                                                                             |
|  404 | `not_found`                                      | 알 수 없는 모델입니다.                                                                                                                          |
|  429 | `rate_limit_exceeded`                            | 요청 한도에 도달했습니다. `Retry-After`에 지정된 초만큼 기다리세요.                                                                                           |
|  500 | `internal_error`                                 | 예기치 않은 내부 오류이며 재시도할 수 있습니다.                                                                                                            |
|  503 | `processing_unavailable` 또는 다른 `processing_*` 코드 | 요청을 완료하지 못했습니다. 대기 시간을 늘리며 재시도하세요.                                                                                                     |

실패한 호출은 크레딧을 사용하지 않습니다.

## 과금

유료 모델 `jev-1.14`와 `jev-1.13`은 `state`, `questions`, 이미지가 차지하는 **입력 토큰에만 과금**합니다. 가격은 **입력 토큰 100만 개당 \$0.0441**입니다. 출력은 생성된 문장이 아닌 타입이 지정된 값이므로 과금하지 않습니다. 각 응답의 `usage.input_tokens`에서 정확한 사용량을 확인해 호출별 비용을 대조할 수 있습니다.

이미지도 같은 입력 토큰 단가를 적용하며 별도의 이미지 요금은 없습니다. 참고로 280×280 이미지는 약 320토큰, 512×512는 약 840토큰, 1024×768은 약 2,200토큰입니다. 실제 사용량은 `usage.input_tokens`에 표시됩니다.

따라서 같은 상태를 여러 번 보내는 것보다 한 번의 호출로 여러 질문을 하는 편이 훨씬 저렴합니다.

### 요청 한도

유료 호출은 계정의 일반 요청 한도를 사용하며, 누적 충전액에 따라 한도가 올라갑니다. 현재 한도와 다음 충전 구간은 대시보드에서 확인하세요. JEV 전용 할당량은 없습니다. 한도를 넘으면 `429 rate_limit_exceeded`와 `Retry-After` 헤더가 반환되니 그 시간만큼 기다린 뒤 다시 시도하세요.

첫 호출 전에 [잔액을 충전](https://beatapi.io/dashboard/billing)하세요. 유료 호출에는 잔액이 필요합니다.

### 무료 JEV 모델(종료)

`jev-1.14-free`와 `jev-1.13-free`는 2026-11-05에 종료되었습니다. 이 이름을 지정한 요청은 더 이상 성공하지 않습니다. `model`을 `jev-1.14`로 바꾸세요. 엔드포인트, `state`, `questions`는 그대로 둡니다.