API 레퍼런스
ollaya serve는 http://localhost:11435에서 두 개의 API를 노출합니다:
/api/*아래의 네이티브 API로, Ollama를 본떠 만들었으며 의사결정과 모델 관리를 담당합니다./v1/*아래의 TypeSafe 호환 API로, TypeSafe와 와이어 수준에서 동일하므로 기존 TypeSafe SDK가 수정 없이 동작합니다. TypeSafe 호환성을 참고하십시오.
| 메서드 | 경로 | 용도 |
|---|---|---|
GET, HEAD |
/ |
생존 확인: Ollaya is running |
GET |
/api/version |
서버 버전 |
POST |
/api/decide |
상태에 대한 타입이 지정된 질문에 답하고, 모델을 로드·언로드합니다 |
GET |
/api/tags |
이 기계에 있는 모델 |
POST |
/api/show |
한 모델의 세부 정보 |
GET |
/api/ps |
메모리에 로드된 모델 |
POST |
/api/pull |
모델 다운로드(진행 상황 스트리밍) |
DELETE |
/api/delete |
모델 삭제 |
POST |
/api/copy |
모델을 새 이름으로 복사 |
POST |
/api/create |
다른 모델로부터 모델 생성(진행 상황 스트리밍) |
POST |
/v1/systemone |
TypeSafe System One |
POST |
/v1/decisions |
/v1/systemone의 별칭 |
GET |
/v1/models |
TypeSafe 모델 목록 |
/api/push와 /api/blobs/:digest는 예약되어 있으며 501 NOT_IMPLEMENTED를 반환합니다. Ollama의 텍스트 엔드포인트(/api/generate, /api/chat, /api/embed)는 404를 반환합니다: 의사결정 모델은 텍스트를 생성하지 않습니다.
관례
- JSON. 요청과 응답 본문은 JSON 객체입니다. 본문은
Content-Type이 무엇이든 JSON으로 파싱되므로curl -d가 그대로 동작합니다. 요청은 최대 8 MiB입니다. - 필드 이름은
snake_case입니다. 알 수 없는 요청 필드는 무시되며,null은 없음을 뜻합니다. - 모델 이름은
[host/][namespace/]model[:tag]이며 대소문자를 구분하지 않습니다. 태그가 없으면latest를 뜻합니다. 응답은 항상laya:latest같은 정규 형식을 씁니다. - 숫자. 확률, 신뢰도,
score,noul은 소수점 네 자리로 반올림됩니다. 듀레이션은 나노초 단위의 정수이고, 타임스탬프는 UTC 기준 RFC 3339입니다. - 스트리밍.
/api/pull과/api/create는 줄바꿈으로 구분된 JSON을 스트리밍하며, 한 줄에 객체 하나씩이고 정확히 하나의{"status":"success"}또는 오류 줄로 끝납니다. 단일 응답을 받으려면"stream": false를 보내십시오. - 요청 ID. 모든 응답은
X-Request-Id를 지니며,/v1/*응답은x-typesafe-request-id도 지닙니다. 클라이언트가 보낸 유효한X-Request-Id는 그대로 되돌려줍니다. - 동시성. 로드된 모델은 한 번에 하나의 요청을 실행하며, 각 요청은 자신의 모든 질문에 한 번의 패스로 답합니다. 같은 모델로 가는 요청은 대기열에 쌓이므로 한꺼번에 더 많이 보내도 더 빨리 끝나지 않고, 각 요청의 왕복 시간에는 대기 시간이 포함됩니다. 한 상태에 대한 모든 질문을 한 요청에서 물으십시오. 로드된 모델이 다르면 병렬로 실행됩니다.
- 암묵적 풀은 없습니다. 어떤 엔드포인트도 부수 효과로 모델을 다운로드하지 않습니다.
ollaya run은 먼저 풀하고, 애플리케이션은/api/pull을 호출합니다.
오류
모든 엔드포인트의 모든 오류는 다음 본문을 가집니다:
{
"error": "model \"laya:xl\" not found, try pulling it first",
"code": "MODEL_NOT_FOUND"
}
| 필드 | 의미 |
|---|---|
error |
사람이 읽는 메시지입니다. 파싱하지 마십시오. 유일하게 고정된 메시지는 Ollama에서처럼 model "<name>" not found, try pulling it first입니다. |
code |
기계가 읽는 코드입니다. 이것으로 분기하십시오. |
detail |
INVALID_REQUEST, TOO_MANY_OPTIONS, INPUT_TOO_LONG, STATE_TRUNCATED에만 있습니다: 모든 검증 문제를 TypeSafe(FastAPI)의 ValidationError 형태로 담습니다: loc, msg, type, 그리고 때때로 ctx입니다. |
| 코드 | HTTP | 발생 시점 | 재시도 |
|---|---|---|---|
INVALID_JSON |
400 | 본문이 없거나 JSON이 아니거나 객체가 아님 | 아니오 |
INVALID_REQUEST |
422 | 본문이 검증을 통과하지 못함; detail이 모든 문제를 나열 |
아니오 |
TOO_MANY_OPTIONS |
422 | 질문의 선택지가 모델의 선택지 예산에 맞지 않음 | 아니오 |
INPUT_TOO_LONG |
422 | state가 65,536 토큰보다 김 |
아니오 |
STATE_TRUNCATED |
422 | /v1/systemone 또는 /v1/decisions가 모델의 컨텍스트에 맞추려고 state의 일부를 버리게 됨 |
아니오 |
UNAUTHORIZED |
401 | OLLAYA_API_KEY가 설정되어 있고 요청에 키가 없음 |
아니오 |
FORBIDDEN |
403 | 브라우저 Origin 또는 Host 헤더가 허용되지 않음 |
아니오 |
MODEL_NOT_FOUND |
404 | 모델(또는 라우터의 대상)이 이 기계에 없음; 풀이라면 레지스트리에 없음 | 아니오 |
NOT_FOUND |
404 | 그런 엔드포인트가 없음 | 아니오 |
METHOD_NOT_ALLOWED |
405 | 엔드포인트는 있지만 메서드가 없음 | 아니오 |
OPERATION_IN_PROGRESS |
409 | 풀 또는 생성이 같은 모델 이름에 쓰는 중 | 끝난 뒤 |
REQUEST_TOO_LARGE |
413 | 본문이 8 MiB 초과 | 아니오 |
QUEUE_FULL |
503 | OLLAYA_MAX_QUEUE개의 요청이 이미 대기 중; Retry-After: 1과 함께 전송 |
예 |
MODEL_LOAD_FAILED |
500 | 모델을 로드할 수 없음(손상된 파일, 메모리, OLLAYA_LOAD_TIMEOUT) |
드묾 |
INFERENCE_FAILED |
500 | 의사결정 도중 러너가 실패 | 예 |
STORAGE_ERROR |
500 | 디스크 가득 참, 권한 또는 I/O | 아니오 |
INTERNAL |
500 | 버그; 서버 로그에 요청 ID 아래 세부 정보가 있음 | 예 |
UNSUPPORTED_MODEL |
501 | 이 빌드가 모델의 형식을 실행할 수 없음 | 아니오 |
NOT_IMPLEMENTED |
501 | 예약된 엔드포인트 | 아니오 |
REGISTRY_ERROR |
502 | 레지스트리에 닿을 수 없거나 유효하지 않음 | 예 |
DIGEST_MISMATCH |
502 | 다운로드가 sha256과 일치하지 않아 폐기됨 | 예 |
코드 집합은 열려 있습니다: 알 수 없는 코드는 HTTP 상태로 처리하십시오. 검증 오류는 모든 문제를 한꺼번에 나열합니다:
{
"error": "state: Field required; questions.urgency.score.criteria: List should have at least 2 items after validation, not 1",
"code": "INVALID_REQUEST",
"detail": [
{"loc": ["body", "state"], "msg": "Field required", "type": "missing"},
{
"loc": ["body", "questions", "urgency", "score", "criteria"],
"msg": "List should have at least 2 items after validation, not 1",
"type": "too_short",
"ctx": {"field_type": "List", "min_length": 2, "actual_length": 1}
}
]
}
스트림이 시작된 뒤에는 실패가 같은 형태로 마지막 줄에 도착합니다. 예를 들어 {"error": "…", "code": "DIGEST_MISMATCH"}입니다. 각 줄을 진행 상황으로 읽기 전에 error가 있는지 확인하십시오.
질문
/api/decide, /v1/systemone, /api/create는 하나의 질문 스키마, 즉 TypeSafe의 스키마를 공유합니다. 한 요청에는 1–256개의 질문이 있고 임의의 id로 키가 잡히며, 답은 같은 순서로 돌아옵니다.
type |
instructions |
criteria |
답변 |
|---|---|---|---|
choice |
선택 | 필수: 객체 레이블 → 설명, 또는 레이블 배열; 2–255개 선택지 | choice, confidence, probabilities |
score |
선택 | 필수: 단계 설명의 배열, 단계 0이 먼저; 2–10개 단계 | score, confidence, legend, probabilities |
noul |
선택 | 선택: {"true": "…", "false": "…"} |
noul |
instructions: 문자열, 객체, 배열 또는null일 수 있습니다. 없거나null이면 모델이 대신 질문 id를 읽으므로,is_spam처럼 서술적인 id만으로도 동작합니다.state: 문자열, 객체 또는 배열이며 최대 65,536 토큰입니다. 모델의 사용 가능한 컨텍스트를 넘으면/api/decide가 이를 잘라내고state_truncated: true를 보고합니다./v1/systemone과/v1/decisions는 답한 모델을detail[0].ctx.model에 담아422 STATE_TRUNCATED를 반환합니다.- 모델 한도. 모든 선택지는 모델 컨텍스트에 들어갈 자리가 필요합니다:
laya:en(512 토큰)은 약 125개,laya:multilingual(1,024)은 250개입니다. 그보다 많으면422 TOO_MANY_OPTIONS입니다. 라우터라면 대상의 한도가 적용됩니다.
답변은 TypeSafe의 형태이며, 필드 순서는 다음과 같습니다:
type |
필드 |
|---|---|
choice |
choice: 가장 가능성이 높은 레이블. confidence. probabilities: 레이블 → 확률, criteria 순서. |
score |
score: 기대 단계 Σ i·pᵢ로, 단계 사이에 있을 수 있습니다. confidence. legend: "0"… → 단계의 설명. probabilities: "0"… → 확률. |
noul |
noul: 그 명제가 성립할 확률. TypeSafe에서처럼 confidence는 없습니다. |
confidence는 TypeSafe의 정규화된 최상위 확률로, 선택지가 K개일 때 (K · pmax − 1) / (K − 1)입니다. 모든 선택지가 똑같이 가능하면 0이고, 한 선택지가 모든 확률을 가지면 1입니다. 공식은 모든 모델에서 같지만, 주어진 신뢰도가 뜻하는 바는 다릅니다: 모델마다 캘리브레이션이 다르므로 자기 데이터에서 모델별로 임계값을 조정하십시오. 확률은 각 모델의 온도로 캘리브레이션됩니다. CUDA GPU에서는 fp16 그래프가 실행되며, 그 답은 근접 동률에서 fp32와 다를 수 있습니다.
keep_alive
요청이 끝난 뒤 모델이 얼마나 오래 로드된 채로 남는지를, Ollama의 시맨틱으로 정합니다:
| 값 | 의미 |
|---|---|
"5m", "1h30m", "300ms", 300, "300" |
요청 뒤 이 시간만큼 로드된 채로 유지 |
0, "0", "0s" |
요청이 끝나는 즉시 언로드 |
-1, "-5m", 음수 값 |
서버가 멈추거나 명시적으로 언로드할 때까지 로드된 채로 유지 |
없음 또는 null |
OLLAYA_KEEP_ALIVE, 기본값 5m |
타이머는 요청이 끝날 때 시작하며, 가장 최근 요청의 값이 이깁니다. 라우터라면 답한 대상에 적용됩니다. /v1/*는 keep_alive를 무시합니다.
의사결정
POST /api/decide
한 번의 포워드 패스로 상태에 대한 타입이 지정된 질문에 답합니다. 본문은 /v1/systemone 본문에 네이티브 옵션을 더한 것이고, 응답은 TypeSafe의 응답에 네이티브 필드를 더한 것이므로 TypeSafe 클라이언트도 파싱할 수 있습니다.
| 필드 | 타입 | 필수 | 비고 |
|---|---|---|---|
model |
string | 예 | 모델 이름 |
state |
string, object 또는 array | 의사결정하려면 예 | 없으면 요청이 모델을 로드하거나 언로드합니다(아래) |
questions |
object | 모델에 내장 질문이 없으면 예 | 모델 자체의 질문을 완전히 대체합니다 |
preset |
string | 아니오 | questions 대신 쓸 프리셋의 이름, 내장 또는 사용자 정의 |
images |
문자열 배열 | 아니오 | 비전 모델용: PNG 이미지를 base64 또는 base64 data: URL로. Decider는 한 장, winnow:e4b-vision은 최대 16장. 이미지 참고 |
keep_alive |
string 또는 number | 아니오 | keep_alive 참고 |
extras |
문자열 배열 | 아니오 | ["laya"]는 모든 답에 laya 자체의 신뢰도와 act 확률을 더합니다 |
stream |
boolean | 아니오 | 예약됨; true는 거부됨 |
curl http://localhost:11435/api/decide -d '{
"model": "laya",
"state": "I was charged twice for my subscription this month. Please refund the second charge.",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices and refunds",
"technical": "Bugs, errors and outages",
"account": "Login, profile and settings"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": ["Can wait", "Needs attention this week", "Needs attention today"]
},
"refund": {
"type": "noul",
"instructions": "The customer asks for money back.",
"criteria": {"true": "Asks for a refund", "false": "Does not ask for a refund"}
}
},
"keep_alive": "10m"
}'
{
"model": "laya:en",
"answers": {
"department": {
"type": "choice",
"choice": "billing",
"confidence": 0.7781,
"probabilities": {"billing": 0.8521, "technical": 0.0611, "account": 0.0868}
},
"urgency": {
"type": "score",
"score": 1.1982,
"confidence": 0.3418,
"legend": {"0": "Can wait", "1": "Needs attention this week", "2": "Needs attention today"},
"probabilities": {"0": 0.1203, "1": 0.5612, "2": 0.3185}
},
"refund": {"type": "noul", "noul": 0.9127}
},
"usage": {"input_tokens": 118, "output_tokens": 0},
"routing": {
"router": "laya:latest",
"model": "laya:en",
"route": "english",
"reason": "English Latin text"
},
"state_truncated": false,
"done_reason": "decide",
"created_at": "2026-09-24T09:30:12.418Z",
"total_duration": 18734512,
"load_duration": 0,
"eval_duration": 16302117
}
| 필드 | 의미 |
|---|---|
model |
답한 모델: 라우터라면 그 대상(laya 요청에는 laya:en) |
answers |
질문 id → 답, 질문 순서대로 |
usage |
읽은 input_tokens; output_tokens는 항상 0 |
routing |
라우터라면: router, 선택된 model, 안정적인 route 키와 정보성 reason. 그렇지 않으면 null. |
state_truncated |
모델 컨텍스트에 맞추려고 상태의 일부를 버렸으면 true |
done_reason |
"decide", "load" 또는 "unload" |
created_at |
응답이 생성된 시각 |
total_duration |
요청을 받은 순간부터 응답까지의 나노초, 대기열 포함 |
load_duration |
모델 로드를 기다린 나노초; 따뜻했으면 0 |
eval_duration |
러너 안에서의 나노초: 토큰화, 포워드 패스, 캘리브레이션 |
"extras": ["laya"]를 쓰면 모든 답에 laya 객체도 붙습니다: confidence(laya의 엔트로피 기반 신뢰도)와 act_probability(모델의 act 헤드에서 나오며, 없으면 null)입니다.
이미지
비전 모델(decider:2b-vision 또는 winnow:e4b-vision)은 상태뿐 아니라 이미지에 대한 질문에도 답합니다. Ollama의 images가 동작하는 방식대로, base64로 인코딩한 이미지를 images에 담아 보내십시오:
curl http://localhost:11435/api/decide -d '{
"model": "decider:2b-vision",
"state": "A photo from the warehouse camera.",
"images": ["'"$(base64 -w0 shelf.png)"'"],
"questions": {
"blocked": {"type": "noul", "instructions": "Is the aisle blocked?"},
"fill": {"type": "score", "instructions": "How full is the shelf?", "criteria": ["empty", "half full", "full"]}
}
}'
- Decider: 요청당 이미지 한 장, PNG만 됩니다. 모델의 전처리가 값 하나까지 재현되므로, 픽셀이 모델 제작자가 디코딩하는 것과 일치해야 합니다. Rust의 JPEG 디코더는 일부 픽셀에서 libjpeg-turbo와 최대 4 레벨까지 다르므로, JPEG는 아직 받지 않습니다: 먼저 PNG로 변환하십시오.
- Decider: 이미지는 모델이 기대하는 대로 32픽셀의 배수로 크기가 조정되고, 그 뒤 16x16픽셀 패치를 최대 4,096개, 즉 약 100만 화소(1024x1024)까지 가질 수 있습니다. 더 큰 이미지는 그렇다고 알려주는 422를 받으니, 먼저 줄이십시오.
- Decider: 질문은 선택지를 최대 10개까지 가집니다. 같은 모델이 텍스트만 있는 요청에도 답합니다.
- Winnow E4B vision: 순서가 있는 PNG를 최대 16장, 질문당 2–64개 선택지, 이미지·상태·질문을 합친 컨텍스트 안에서. 일치하는 프로젝터는 같은 작성자 리비전에서 별도로 다운로드합니다. 기존 Winnow 텍스트 태그는 그것을 불러오지 않습니다.
- 이미지를 읽지 않는 모델은
images가 있는 요청에 422로 답합니다.
/v1/systemone과 /v1/decisions는 이미지 필드가 없는 TypeSafe API와 동일하게 유지됩니다.
로드와 언로드. state와 questions가 없는 요청은 절대 의사결정하지 않습니다. keep_alive가 없거나 양수·음수면 모델(라우터라면 모든 대상)을 로드하고 done_reason: "load"를 반환합니다. keep_alive: 0이면 언로드합니다("unload"). ollaya run은 이 방식으로 미리 로드하고, ollaya stop은 언로드합니다.
curl http://localhost:11435/api/decide -d '{"model": "laya:en", "keep_alive": -1}'
curl http://localhost:11435/api/decide -d '{"model": "laya:en", "keep_alive": 0}'
의사결정은 저장된 데이터에 부수 효과가 없으므로 재시도해도 안전합니다.
프리셋
프리셋은 이름이 붙은 질문 집합입니다. 여섯 개가 내장되어 있고(triage, email, guard, moderation, router, agent), 직접 만든 것을 저장할 수도 있습니다. questions 대신 "preset": "NAME"을 /api/decide로 보내십시오.
curl http://localhost:11435/api/presets/create -d '{
"name": "billing-check",
"description": "Billing, and how upset the customer is",
"questions": {
"billing": {"type": "noul", "instructions": "The message is about a charge, an invoice or a refund."},
"tone": {"type": "choice", "instructions": "How does the customer sound?", "criteria": {"calm": null, "annoyed": null, "angry": null}}
}
}'
curl http://localhost:11435/api/decide -d '{"model": "winnow:e4b", "state": "I was charged twice this month.", "preset": "billing-check"}'
| 엔드포인트 | 본문 | 효과 |
|---|---|---|
GET /api/presets |
– | 내장 프리셋, 그다음 사용자 정의: name, builtin, description, 질문 id, modified_at |
POST /api/presets/create |
name, questions, description(선택) |
사용자 정의 프리셋 저장, 같은 이름이면 대체 |
POST /api/presets/show |
name |
질문과 함께 프리셋 하나 |
DELETE /api/presets/delete |
name |
사용자 정의 프리셋 삭제 |
이름은 소문자, 숫자, -, _로 된 1–64자입니다. 내장 이름은 재사용할 수 없고(422), 삭제할 수도 없으며(403), 알 수 없는 이름은 404입니다. 사용자 정의 프리셋은 모델 옆에 저장되므로, 서버의 모든 클라이언트가 같은 것을 봅니다.
라우터
laya(laya:latest) 같은 라우터는 가중치가 없습니다: 요청마다 대상 중 하나를 골라 그것이 답합니다. laya는 state만 읽습니다:
| 상태 | route |
답하는 모델 |
|---|---|---|
| 영어 | english |
laya:en |
| 대부분 비라틴 문자(아랍어, 키릴 문자, CJK 등) | multilingual |
laya:multilingual |
| 라틴 문자지만 영어가 아님(터키어, 독일어 등) | multilingual |
laya:multilingual |
| 문자 없음 | english(기본값) |
laya:en |
강조 부호 없이 대문자로만 된 짧은 텍스트, 예컨대 카드 명세서의 가맹점 이름(MIGROS KADIKOY ISTANBUL TR), SKU, 사용자 이름은 보통 식별되지 않아 laya:en으로 갑니다. 언어를 안다면 laya:multilingual이나 laya:en을 직접 요청하십시오. 응답의 model이 어느 체크포인트가 답했는지 알려줍니다.
라우팅은 마이크로초가 걸립니다. route로 분기하고, reason으로는 절대 분기하지 마십시오. 그 문구는 바뀔 수 있습니다. laya:typed-decisions는 라우터가 절대 고르지 않으니 직접 요청하십시오.
로컬 모델 나열
GET /api/tags
이 기계에 있는 모델을 최신순으로 나열합니다. 각 항목은 name, model(같은 값), modified_at, 바이트 단위의 size, digest(매니페스트의 sha256, 순수 16진수), details를 가집니다: parent_model, format(onnx, gguf 또는 router), family, families, parameter_size, quantization_level(보유한 정밀도. 예: F16/F32, 또는 GGUF 모델의 양자화. 예: Q8_0).
{
"models": [
{
"name": "laya:en",
"model": "laya:en",
"modified_at": "2026-09-24T08:11:02.117Z",
"size": 853634822,
"digest": "bf30e4654e9483ff1e6a4fe6fb21b8a71baff6c8a01013046e7d13339020efd7",
"details": {
"parent_model": "",
"format": "onnx",
"family": "laya",
"families": ["laya"],
"parameter_size": "421M",
"quantization_level": "F16/F32"
}
}
]
}
모델 세부 정보 표시
POST /api/show
curl http://localhost:11435/api/show -d '{"model": "laya:en"}'
| 필드 | 의미 |
|---|---|
license |
라이선스 텍스트 |
modelfile |
모델을 다시 만드는 Modelfile |
parameters |
모델에 설정된 파라미터, 한 줄에 name value 하나씩. 예: precision fp32 |
questions |
내장 질문, 또는 null |
router |
라우터라면: strategy, default, routes(route → model). 그렇지 않으면 null. |
details |
/api/tags와 같음 |
model_info |
general.architecture, general.languages, general.source(고정된 Hugging Face 저장소), 그리고 laya.context_length 같은 패밀리별 키. general.languages는 모델이 훈련·평가된 언어를 나열합니다(많으면 multilingual). 다국어 기반으로 만든 모델이 다른 언어도 읽을 수 있으니 데이터에서 측정하십시오. |
capabilities |
답하는 질문 유형(choice, score, noul), 그리고 act 헤드가 있으면 act |
modified_at |
/api/tags와 같음 |
라우터는 대상으로 해석되지 않고 자기 자신으로 표시됩니다.
실행 중인 모델 나열
GET /api/ps
로드된 모델을 이름순으로 나열합니다. 라우터는 절대 나타나지 않고, 로드된 대상이 나타납니다. 각 항목은 name, model, size(메모리, RAM + VRAM), digest, details(실제로 로드된 정밀도: F16 또는 F32, 혹은 GGUF 모델의 양자화), expires_at(언로드될 시각, 로드된 채로 유지되면 null), size_vram, context_length, device(cpu, cuda:0, metal 등)를 가집니다.
모델 풀
POST /api/pull
{"model": "laya:en"}
모델을 로컬 저장소로 내려받고 모든 blob을 sha256으로 검증합니다. 라우터를 풀하면 그것이 라우팅하는 모든 모델도 함께 풀합니다. 이 기계에 필요한 레이어만 다운로드되고, 모델 간에 공유되는 blob은 한 번만 다운로드되며, 중단된 다운로드는 이어받습니다.
응답은 Ollama의 상태 문자열과 함께 진행 상황을 스트리밍합니다:
{"status":"pulling manifest"}
{"status":"pulling 891102d37268","digest":"sha256:891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c","total":842609210,"completed":420557117}
{"status":"pulling 891102d37268","digest":"sha256:891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c","total":842609210,"completed":842609210}
{"status":"verifying sha256 digest"}
{"status":"writing manifest"}
{"status":"success"}
모델은 writing manifest 이후에야 /api/tags에 나타납니다. 라우터라면 맨 끝에 success가 하나 있습니다. 파싱되지 않는 이름, 레지스트리에 없는 모델, 닿을 수 없는 레지스트리는 스트림이 시작되기 전의 평범한 HTTP 오류(422, 404, 502)이므로 curl --fail이 동작합니다. "stream": false면 완료 시 응답은 {"status": "success"}입니다. 같은 이름을 두 번째로 풀하면 진행 중인 풀에 합류합니다. 재시도해도 안전합니다.
모델 삭제
DELETE /api/delete
{"model": "triage"}
이름과, 다른 모델이 쓰지 않는 blob을 제거합니다. 로드된 모델은 요청이 끝나면 언로드됩니다. 라우터를 삭제해도 그 대상은 남습니다. 응답은 빈 본문의 200이고, 이름이 없으면 404 MODEL_NOT_FOUND입니다. 타임아웃 뒤에는 이를 성공으로 취급하십시오.
모델 복사
POST /api/copy
{"source": "laya:en", "destination": "my-guardrail"}
모델을 새 이름으로 복사하며, 기존 대상이 있으면 덮어씁니다. 응답은 빈 본문의 200입니다.
모델 생성
POST /api/create
ollaya create -f Modelfile 뒤의 API입니다: CLI가 Modelfile과 그것이 지목하는 파일을 읽어 그 내용을 JSON으로 보냅니다.
| 필드 | 타입 | 필수 | 비고 |
|---|---|---|---|
model |
string | 예 | 만들 이름 |
from |
string | 예 | 로컬 모델, 라우터일 수도 있음. 절대 풀하지 않습니다. |
questions |
object | 아니오 | 내장 질문, 의사결정 요청처럼 검증됨 |
calibration |
object | 아니오 | temperature: 최대 3개 숫자(choice, score, noul). temperature_by_options: "<type>:<2|3-5|6-10|11+>" → 숫자. |
parameters |
object | 아니오 | precision: "fp16" 또는 "fp32", 그래프 하나를 고정 |
license |
string 또는 array | 아니오 | 라이선스 텍스트 |
description |
string | 아니오 | 한 줄, /v1/models와 ollaya show가 표시 |
stream |
boolean | 아니오 | 기본값 true |
curl http://localhost:11435/api/create -d '{
"model": "triage",
"from": "laya:en",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": ["billing", "technical", "account"]
}
},
"parameters": {"precision": "fp32"},
"description": "Support ticket triage"
}'
스트림은 상속된 레이어마다 using existing layer sha256:…를, 새 레이어마다 creating new layer sha256:…를 보고한 뒤 writing manifest와 success를 보고합니다. 레이어는 내용 주소 지정되므로, 생성을 반복해도 같은 모델이 나옵니다.
버전
GET /api/version
{"version": "0.1.0"}
TypeSafe 호환 엔드포인트
| 엔드포인트 | 설명 |
|---|---|
POST /v1/systemone |
요청: model, state(필수), questions. 응답: 정확히 model, answers, usage. |
POST /v1/decisions |
/v1/systemone의 별칭 |
GET /v1/models |
로컬 모델, {"models": [{"name", "description", "release_date"}]} 형태 |
/v1/*는 keep_alive, extras 같은 네이티브 필드를 무시하며, 응답에 네이티브 필드를 절대 더하지 않습니다. 오류는 /api/*와 같은 본문을 쓰며, TypeSafe SDK가 이를 올바르게 읽습니다. TypeSafe 호환성을 참고하십시오.
보안
서버는 127.0.0.1:11435에 바인딩되며, Ollama처럼 로컬 호출자를 신뢰합니다. 다른 주소로 바인딩하면(OLLAYA_HOST=0.0.0.0) 포트에 닿을 수 있는 누구나 의사결정을 실행하고 모델을 풀·삭제·생성할 수 있으므로:
OLLAYA_API_KEY:GET /,HEAD /, CORS 프리플라이트를 제외한 모든 요청이Authorization: Bearer <key>를 요구하게 합니다. 그렇지 않으면 답은401 UNAUTHORIZED입니다. TypeSafe SDK는 이 방식으로 키를 보내고,ollayaCLI는$OLLAYA_API_KEY를 보냅니다. 서버는 루프백을 넘어 키 없이 리슨할 때 경고를 로그에 남깁니다.- TLS는 서버가 종료하지 않습니다. 원격 접근에는 앞에 리버스 프록시를 두십시오.
- 브라우저.
Origin헤더가 있는 요청은localhost,127.0.0.1,0.0.0.0,[::1](모든 포트), 앱과 편집기 웹뷰, 그리고OLLAYA_ORIGINS에 있는 오리진(쉼표로 구분,*와일드카드)에서만 허용됩니다. 루프백 서버는 예상치 못한Host헤더도 거부하여 DNS 리바인딩을 막습니다. - 여러분의 데이터. 상태와 질문은 절대 로그에 남지 않고 오류에도 되돌아오지 않습니다.
| 변수 | 기본값 | 효과 |
|---|---|---|
OLLAYA_HOST |
127.0.0.1:11435 |
바인드 주소; 클라이언트의 대상. 루프백 주소는 [::1]에서도 리슨하므로, Windows 프로그램이 WSL의 서버에 localhost로 지연 없이 닿습니다 |
OLLAYA_API_KEY |
설정 안 함 | Authorization: Bearer <key> 요구 |
OLLAYA_ORIGINS |
설정 안 함 | 추가로 허용할 브라우저 오리진 |
OLLAYA_KEEP_ALIVE |
5m |
기본 keep_alive |
OLLAYA_MAX_LOADED_MODELS |
3 |
로드된 모델 한도 |
OLLAYA_MAX_QUEUE |
512 |
503 QUEUE_FULL 전에 처리 중인 요청 |
OLLAYA_LOAD_TIMEOUT |
5m |
500 MODEL_LOAD_FAILED 전의 로드 기한 |
OLLAYA_DEVICE |
auto |
auto, cpu, cuda 또는 cuda:<n> |
OLLAYA_MODELS |
~/.ollaya/models |
모델 저장소 |
OLLAYA_REGISTRY |
ollaya.dev |
이름에 쓰는 기본 레지스트리 호스트 |