자람AI 연동 API

업체 시스템이 우리 영상·자막·문항·질의응답을 가져가는 규격입니다. v1 · 2026-09-15 갱신 — 바뀐 점

인증공통 규약질의응답 자료 조회문제은행분류 관리자료 등록게시물 등록영상 등록작업 시키기 완료 알림접근 제어영상 수신 재시도오류바뀐 점

기본 주소https://api.jaramai.co.kr/api/v1
경로아래 표기는 모두 이 기본 주소 뒤에 붙습니다 — 예: /api/v1/videos
형식요청·응답 모두 JSON (UTF-8). 자막만 srt·vtt 로도 받습니다.
인증X-API-Key 헤더 한 장
요금조회는 과금하지 않습니다. 부를 때마다 AI 가 도는 /api/v1/ask 와 실제로 처리가 도는 작업(인코딩·자막·문제)만 토큰을 씁니다 — 작업은 끝난 뒤 실측으로 계산하고 실패하면 물리지 않습니다.

인증

발급받은 키를 헤더에 넣습니다. 키가 없거나 틀리면 401 입니다.

curl -H "X-API-Key: 발급받은키" https://api.jaramai.co.kr/api/v1/categories
키는 곧 신원입니다. 브라우저(자바스크립트)에서 직접 부르면 키가 그대로 노출됩니다. 업체 서버에서 호출하시고, 키가 새어 나갔다고 판단되면 바로 알려 주세요 — 재발급해 드립니다.

GET/api/v1/me

이 키(업체)가 어떤 상품을 계약했는지 돌려줍니다. 연동 화면에서 계약 안 한 기능을 미리 가리거나 막을 때 쓰세요 — 실제 차단은 각 기능 호출에서 403 not_licensed 로도 막지만, 미리 알면 "안 되는 버튼"을 누르게 두지 않아도 됩니다.

curl -H "X-API-Key: 발급받은키" https://api.jaramai.co.kr/api/v1/me
{
  "client": { "name": "○○학원" },
  "features": {
    "encode": true,       // 동영상 인코딩
    "transcribe": true,   // 자막 + 요약
    "quizgen": false,     // 문제 생성 (AI 서비스에 포함)
    "tutor": false        // AI 서비스 (질의응답·게시물)
  },
  "quiz_per_video": 10
}

features 의 각 값이 true 면 그 기능을 쓸 수 있습니다. 문제 생성(quizgen)은 AI 서비스(tutor)에 포함되어 둘이 함께 켜지고 함께 꺼집니다.

공통 규약

증분 조회 — 바뀐 것만 가져갑니다

목록형 조회는 모두 같은 규칙입니다. 처음에는 since 로 시작하고, 그 다음부터는 응답이 준 next_cursor 를 그대로 넣으면 됩니다.

파라미터설명
since이 시각 이후 것만 (예: 2026-08-01T00:00:00Z)
cursor지난 응답의 next_cursor. since 보다 우선합니다
limit한 번에 받을 개수. 기본 100, 최대 500
커서를 그대로 보관하세요. 시각만으로 이어받으면 같은 초에 들어온 자료가 페이지 경계에서 누락될 수 있습니다. 커서는 (시각, 번호) 를 함께 담고 있어 그 구멍을 막습니다. 받은 것이 0건이어도 커서는 돌려드리니 그대로 저장해 두시면 됩니다.
{
  "count": 100,
  "has_more": true,
  "next_cursor": "MjAyNi0wOC0xNiAxMDoxMjozM3wxNDIx",
  "videos": [ ... ]
}

count(이번에 받은 개수) · has_more · next_cursor 세 칸은 어느 조회나 같고, 자료가 담기는 칸 이름만 엔드포인트마다 다릅니다.

엔드포인트자료가 담기는 칸
/api/v1/videosvideos
/api/v1/subtitlessubtitles
/api/v1/quizquestions
/api/v1/chat-logslogs
/api/v1/boardposts
/api/v1/categoriescategories

호출 제한

업체별로 셉니다 — 조회 분당 60회, 질의응답 분당 30회. 넘기면 429 입니다.

영상을 가리키는 방법

영상 하나를 지정하는 자리({video})에는 우리 번호와 업체 관리번호 어느 쪽을 넣어도 됩니다. 등록하실 때 관리번호를 함께 주시면 그 값으로 조회할 수 있습니다.

질의응답

GET/api/v1/categories

질문을 걸 수 있는 분류(과목·강좌) 목록입니다. /api/v1/ask 의 category_id 가 여기서 나옵니다.

POST/api/v1/ask

분류에 매달린 자료·자막·게시물 세 갈래에서 근거를 찾아 답합니다.

본문설명
question필수 · 2~2,000자
category_id필수 · /api/v1/categories 의 번호
k근거 건수(생략 가능)
curl -X POST https://api.jaramai.co.kr/api/v1/ask \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{"question":"주주총회 소집 통지 기한은?","category_id":12}'
{
  "found": true,
  "answer": "...",
  "category": { "id": 12, "name": "상법", "path": "..." },
  "sources": [ { "cite": "[자료 3]", "text": "..." } ],
  "usage": { "tokens": 1840 }
}
근거가 없으면 지어내지 않습니다. 찾지 못하면 found:false 로 답하고 토큰도 쓰지 않습니다(AI 를 부르지 않으므로). sources[].cite 는 답변 본문의 인용 표시와 문자까지 같으니 그대로 대조하시면 됩니다.

자료 조회

모두 무과금이고, 위의 증분 규칙(since·cursor·limit)을 공통으로 받습니다.

엔드포인트주는 것 / 추가 파라미터
GET/api/v1/videos 영상 목록 · category, status(encoded·subtitled·summarized)
GET/api/v1/videos/{video}영상 한 건
GET/api/v1/videos/{video}/subtitle 자막 · format=json(기본)·srt·vtt
GET/api/v1/videos/{video}/summary강의 요약
GET/api/v1/subtitles자막 목록(증분) · category
GET/api/v1/quiz 문항 · video, category — AI 가 생성한 문항을 그대로 드립니다. 승인/반려는 가져가신 뒤 업체에서 하세요. 각 문항에 AI 자체검수 결과(check)를 참고용으로 함께 드립니다
GET/api/v1/chat-logs 질의 기록 · category, found, origin
GET/api/v1/board 게시물 Q&A · category, answered, q
POST/api/v1/board 게시물 등록 · category, title, question → 게시물 등록
계약한 상품만 열립니다. 자막을 계약하지 않았다면 자막 조회는 403 입니다. 또한 업체 자료만 보입니다 — 남의 분류·영상 번호를 넣으면 404 입니다.

문제은행 (문항 가져오기)

GET/api/v1/quiz

quizgen 이 만든 문항(객관식·OX)을 있는 그대로 드립니다. 승인·반려는 가져가신 뒤 업체에서 하세요 — 우리는 게이트를 두지 않습니다. 다른 조회처럼 무과금이고 증분 규칙 (since·cursor·limit)을 그대로 받습니다.

파라미터설명
video이 영상 문항만 — 우리 번호나 업체 관리번호(external_key) 어느 쪽이든
category이 분류(소분류) 문항만 — /api/v1/categories 의 번호
since·cursor·limit증분 조회(공통 규약과 동일)
curl -H "X-API-Key: 발급받은키" \
  "https://api.jaramai.co.kr/api/v1/quiz?video=LEC-1001"
{
  "count": 20,
  "has_more": false,
  "next_cursor": "...",
  "questions": [
    {
      "id": 5500,
      "media_id": 3000000056,
      "video": { "number": 1, "title": "1강 총칙", "external_key": "LEC-1001" },
      "type": "mc",
      "question": "다음 중 ... 옳은 것은?",
      "options": ["토테미즘", "샤머니즘", "애니미즘", "조상숭배", "다신교"],
      "answer": "3",
      "explanations": { "basic": "..." },
      "source_ms": 128000,
      "check": "ok",
      "updated_at": "2026-09-08T15:09:59+09:00"
    }
  ]
}
문항 칸설명
id문항 번호(우리 기준, 고유). 같은 문항이 다시 와도 이 번호로 중복을 걸러 쓰세요
video이 문항이 나온 영상 — external_key 로 회차에 매답니다
typemc(객관식) · ox
options객관식 보기 배열. OX 문항은 없습니다
answer객관식은 보기 번호(1부터) 문자열, OX 는 "O"·"X"
explanations해설(키·값). 옛 문항은 {"standard":"..."} 하나만 있을 수 있음
source_ms강의 안에서 이 문항이 나온 지점(밀리초) — 플레이어에서 그 자리로 보낼 때
checkAI 자체검수 결과(참고용) — ok·flawed·mismatch·split 등. 게이트가 아니라 업체가 우선순위 정할 때 쓰라고 함께 드립니다
승인 게이트는 업체가 쥡니다. 우리는 AI 가 만든 문항을 그대로 내보내고, 학생에게 낼지(승인) 말지(반려)는 가져가신 업체 시스템에서 정하세요. check 는 그 판단을 돕는 힌트일 뿐입니다.

분류 관리 (대·중·소)

업체가 자기 칸의 분류를 직접 만들고 고치고 지웁니다. 여기서 만든 분류는 조회용 GET /api/v1/categories 와 콘솔에 바로 나타납니다 — 영상은 이렇게 만든 가장 아래 분류(소)에 매답니다.

깊이는 parent_id 로 정해집니다. 상위를 주지 않으면 대분류, 대분류를 상위로 주면 중분류, 중분류를 상위로 주면 소분류 — 대 > 중 > 소 3단까지입니다. 소분류 아래로 더 만들려 하면 422 category_too_deep 입니다. 응답의 depth 가 1=대 · 2=중 · 3=소 입니다.

POST/api/v1/categories

분류 하나를 만듭니다. 계약과 무관하게 모든 업체가 쓸 수 있습니다 — 분류는 상품이 아니라 업체 계정에 딸린 것이라, 인코딩만 계약하셔도 영상을 담을 자리를 만드셔야 하기 때문입니다. 잠그고 싶으시면 접근 제어에서 categories.write 를 끄시면 됩니다.

본문설명
name필수 · 분류 이름(최대 60자)
parent_id상위 분류 번호. 생략하면 대분류, 주면 그 아래(중·소)로 만듭니다 — /api/v1/categories 의 번호
field분야 표시(선택, 최대 60자). 비우면 상위 분류의 분야를 물려받습니다
active사용 여부(true·false). 생략하면 true
sort정렬 순서(0 이상 정수). 생략하면 0
curl -X POST https://api.jaramai.co.kr/api/v1/categories \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{"name":"상법", "parent_id":10}'
{
  "category": {
    "id": 42,
    "parent_id": 10,
    "depth": 2,
    "name": "상법",
    "path": "법학 > 상법",
    "field": "법학",
    "sort": 0,
    "active": true
  }
}

응답은 201 입니다. 없는 상위 번호이거나 남의 업체 분류를 상위로 주면 — 존재를 숨기기 위해 — 똑같이 404 category_not_found 로 답합니다.

PUT/api/v1/categories/{id}

이름·분야·사용여부·정렬을 고칩니다. 부모를 바꾸는 트리 재배치는 받지 않습니다 — 자리를 옮기면 그 아래 가지의 깊이가 어긋나므로, 옮기려면 새로 만드는 편이 안전합니다. 보낸 칸만 반영하고, 없는 번호이거나 업체 분류가 아니면 404 입니다.

본문설명
name바꿀 이름(선택, 최대 60자)
field분야(선택)
active사용 여부(선택)
sort정렬 순서(선택)
curl -X PUT https://api.jaramai.co.kr/api/v1/categories/42 \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{"name":"상법(개정)", "active":false}'

DELETE/api/v1/categories/{id}

분류 하나를 지웁니다. 하위 분류·영상·과정이 매달려 있으면 409 category_in_use 로 막고, 무엇이 걸려 있는지 in_use 에 children·videos·courses 개수로 돌려줍니다 — 옮길지 지울지는 부르는 쪽이 정합니다.

curl -X DELETE https://api.jaramai.co.kr/api/v1/categories/42 \
  -H "X-API-Key: 발급받은키"
{ "deleted": true, "id": 42 }
// 매달린 것이 있을 때
{ "error": "category_in_use", "message": "...",
  "in_use": { "children": 2, "videos": 5, "courses": 0 } }

자료 등록 (챗봇 근거 문서)

질의응답(/api/v1/ask)이 근거로 삼는 자료(문서)를 올립니다. 올린 문서는 텍스트를 뽑아 분류에 매달아 임베딩하고, 이후 그 분류(및 하위)로 물어보면 근거로 쓰입니다 — 콘솔 [자료관리]에 올리는 것과 같은 자리에 들어갑니다.

분류에 매답니다. category_id 는 소분류(대 > 중 > 소의 말단)를 권합니다 — 상위(대·중)에 매달면 그 아래 전체에 걸립니다. 텍스트를 뽑을 수 있는 txt·pdf 만 받습니다(스캔 PDF·hwp·이미지·영상은 422).

POST/api/v1/documents

문서 하나를 올립니다. 파일(file) 또는 본문 텍스트(text) 중 하나는 필수입니다. 파일이면 multipart/form-data 로 보냅니다.

본문설명
category_id필수. 문서를 매달 분류 번호(소분류 권장) — /api/v1/categories 의 번호
title필수 · 자료 제목(최대 200자)
filetxt·pdf 파일(최대 200MB). text 없이 보낼 때 필수
text직접 입력하는 본문 텍스트. file 없이 보낼 때 필수
curl -X POST https://api.jaramai.co.kr/api/v1/documents \
  -H "X-API-Key: 발급받은키" \
  -F "category_id=42" -F "title=상법 요약" -F "file=@summary.pdf"
{
  "ok": true,
  "document": {
    "id": 21,
    "title": "상법 요약",
    "category_id": 42,
    "filename": "summary.pdf",
    "status": "embedding"
  }
}

응답은 201 이고, 임베딩은 백그라운드로 진행됩니다(status 는 이후 완료됩니다). 없는 분류이거나 남의 업체 분류면 404 category_not_found, 추출 가능한 텍스트가 없으면 422(extract_failed · too_short · no_content) 입니다. 이 기능은 접근 제어의 documents.write 로 잠글 수 있습니다.

DELETE/api/v1/documents/{id}

올린 문서 하나를 지웁니다(임베딩 조각도 함께 정리). 자기 업체 자료만 지울 수 있고, 없는 번호이거나 업체 자료가 아니면 404 document_not_found 입니다. documents.manage 스코프로 잠급니다.

curl -X DELETE https://api.jaramai.co.kr/api/v1/documents/21 \
  -H "X-API-Key: 발급받은키"
{ "ok": true, "id": 21 }

게시물 등록 (학습 Q&A)

업체 게시판의 학습 Q&A(질문 글)을 올립니다. 올린 글은 분류(교수)에 매달아 임베딩되어 콘솔 [게시물관리]에 나타나고, 질의응답(/api/v1/ask)의 근거로도 쓰입니다. 조회는 GET /api/v1/board 입니다.

분류(교수)에 매답니다. category 는 소분류(회차) 또는 중분류(교수) 번호를 줍니다 — 소분류를 주면 그 상위 중분류(교수)로 자동 연결됩니다. 그 분류에 교수 연동(external_teacher_id)이 없으면 422 입니다.

POST/api/v1/board

게시물(질문 글) 하나를 올립니다. application/json 으로 보냅니다.

본문설명
category필수. 글을 매달 분류 번호(소분류=회차 권장 · 중분류=교수) — /api/v1/categories 의 번호
title필수 · 글 제목(최대 500자)
question필수 · 질문 내용
answer답변(있으면). 없으면 미답변 글로 등록
posted_at등록일(YYYY-MM-DD). 생략 시 오늘
curl -X POST https://api.jaramai.co.kr/api/v1/board \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{"category":42,"title":"상법 질문","question":"주주총회 결의요건이 궁금합니다."}'
{
  "ok": true,
  "id": 128,
  "board_id": 128,
  "category": 42,
  "external_teacher_id": 7
}

응답은 201 이고, 질문 텍스트는 임베딩되어 콘솔 게시물관리·질의응답 근거로 반영됩니다. 없는 분류이거나 남의 업체 분류면 404 not_found, 그 분류에 교수 연동(external_teacher_id)이 없으면 422 not_linked 입니다. 이 기능은 접근 제어의 board.write 로 잠급니다.

영상 등록

POST/api/v1/videos

업체 영상을 우리 쪽에 등록합니다. 같은 목록을 다시 보내도 두 벌이 되지 않습니다 — external_key(업체 관리번호)가 같으면 갱신합니다. 그래서 실패 시 그대로 다시 보내면 됩니다.

curl -X POST https://api.jaramai.co.kr/api/v1/videos \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{
    "category_id": 12,
    "pipeline": ["encode", "transcribe"],
    "items": [
      { "external_key": "LEC-1001", "number": 1, "title": "1강 총칙", "url": "https://cdn.example.com/1.mp4" }
    ]
  }'
칸설명
category_id필수. 영상을 매달 소분류 번호 — /api/v1/categories 의 가장 아래 분류
items[]필수. 한 번에 최대 500건. 각 항목은 title(필수) · external_key · number · url 또는 path · duration · thumb_url · published_at
pipeline등록 직후 자동 진행할 단계 — encode · transcribe · quizgen. 계약한 단계만 반영됩니다. 생략하면 등록만 하고 멈춥니다
presets뽑을 화질(최대 3). 생략하면 기본 화질로 뽑습니다
storage_profile_id비공개 저장소(FTP·SFTP)에서 path 로 주실 때. 미리 등록해 둔 접근 설정 번호 — 저장소가 한 곳뿐이면 생략하셔도 됩니다

응답은 201 이고 added·updated·skipped 와 함께 이번에 보내신 영상만 우리 번호(media_id)와 함께 돌아옵니다 — 이어서 작업을 시킬 때 그 번호를 쓰시면 됩니다.

등록할 때 원본이 그 자리에 있는지 확인합니다. 파일을 내려받지는 않고 있는지만 봅니다(몇 GB 라도 즉시). pipeline 을 주시면 원본 확보부터 우리가 바로 시작합니다 — 따로 부르실 것이 없습니다.

작업 시키기

이미 등록된 영상에 대해 인코딩 · 자막 · 문제 생성을 요청합니다. 등록할 때 pipeline 을 주셨다면 따로 부르지 않아도 자동으로 진행됩니다.

POST/api/v1/jobs

curl -X POST https://api.jaramai.co.kr/api/v1/jobs \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{ "type": "encode", "video": "LEC-1001" }'
칸설명
type필수. encode · transcribe · quizgen (계약한 것만)
video필수. 우리 번호(media_id) 또는 업체 관리번호(external_key)
presetsencode 일 때 뽑을 화질(최대 3). 생략하면 등록할 때 정해진 화질, 그것도 없으면 기본 화질
aspectencode 일 때 결과물 비율 — 16:9 · 9:16(쇼츠) · 1:1 · 4:3 · 3:4 · 21:9 · 9:21. 생략하면 원본 비율 그대로. «원본 그대로» 화질과는 함께 못 씁니다
fitaspect 를 줬을 때 앉히기 — letterbox(여백 두기, 기본) · cover(꽉 채우기, 가장자리 잘림)
formatencode 일 때 출력 형식 — ["mp4"] · ["hls","dash"] · ["mp4","hls","dash"]. 생략하면 업체 인코더 설정의 기본값(대개 MP4). HLS/DASH 는 outputs[] 에 kind: "package" 로 옵니다
같은 영상·같은 단계는 한 작업입니다. 재시도로 두 번 들어와도 새 작업이 생기지 않고 과금도 한 번입니다.
원본을 아직 받아오는 중이면 202 에 "job": null 로 답합니다 — 거절이 아니라 예약이고, 그 자리에서 원본 확보를 시작해 확보되는 대로 이어서 돕니다.
다만 그 영상에 원본 위치(url·path)가 아예 없으면 기다려도 시작할 것이 없으므로 422 source_missing 로 답합니다 — /api/v1/videos 로 위치를 등록한 뒤 다시 시켜 주세요.

GET/api/v1/jobs · GET/api/v1/jobs/{id}

진행 상태를 봅니다. 목록은 증분 규칙을 그대로 따르고 type·status·video 로 좁힐 수 있습니다.

{ "job": { "id": "9f3c…", "type": "encode", "status": "processing", "progress": 40,
           "video": { "media_id": 3000000009, "external_key": "LEC-1001", "title": "1강 총칙" },
           "created_at": "2026-08-27T10:00:00+09:00", "started_at": "2026-08-27T10:00:12+09:00" } }

status 는 queued · processing · done · failed 넷입니다. failed 면 error 칸에 이유가 담깁니다. 자막·요약·문항은 자료 조회로 가져가시고, 인코딩 결과물은 아래 outputs 링크로 받습니다.

GET/api/v1/jobs/{id}/output/{filename} — 인코딩 결과물

인코딩 작업이 done 이 되면 작업 조회·영상 조회(/api/v1/videos/{video})·완료 알림에 outputs[] 가 붙습니다. 화질마다 한 줄이고 링크가 둘입니다.

"outputs": [
  { "name": "1080p", "filename": "pj-a52ab741d375.1080p.mp4", "size": 113843400,
    "download":   "https://api.jaramai.co.kr/api/v1/jobs/9f3c…/output/pj-a52ab741d375.1080p.mp4",
    "play_url":   "https://api.jaramai.co.kr/api/v1/jobs/9f3c…/output/pj-a52ab741d375.1080p.mp4?expires=1791966518&sig=1f3e…",
    "expires_at": "2026-10-14T17:28:38+09:00" },
  { "name": "HLS/DASH", "kind": "package", "filename": "pj-a52ab741d375", "size": 11771811, "formats": ["hls", "dash"], "files": 8,
    "hls_url":  "https://api.jaramai.co.kr/api/v1/jobs/9f3c…/stream/1791966518/47484abe…/playlist.m3u8",
    "dash_url": "https://api.jaramai.co.kr/api/v1/jobs/9f3c…/stream/1791966518/47484abe…/playlist.mpd",
    "expires_at": "2026-10-14T17:28:38+09:00" }
]
링크쓰임
download서버끼리. X-API-Key 를 붙여 받습니다
play_url브라우저 플레이어용. 서명이 붙어 있어 키 없이 열립니다 — <video src> 에 그대로 꽂으세요. Range 를 받으므로 구간 탐색(seek)이 됩니다. 30일 뒤 만료되며, 작업이나 영상을 다시 조회하면 새 주소를 드립니다
kind: "package"HLS/DASH 스트리밍 묶음 — 출력 형식에 HLS·DASH 를 켠 업체에만 한 줄 붙습니다(파일이 아니라 폴더라 download 가 없습니다). hls_url(.m3u8)·dash_url(.mpd)을 플레이어에 꽂으면 조각(segment)은 같은 주소 아래에서 알아서 가져갑니다 — 서명이 경로에 들어 있어 조각마다 열쇠가 필요 없습니다. HLS 는 Safari 가 바로 열고 다른 브라우저는 hls.js, DASH 는 dash.js 를 쓰세요. 결과물 전송(FTP·SFTP·S3)을 쓰시면 관리번호/stream/ 폴더로 같은 구조가 갑니다
결과물을 업체 저장소(FTP·SFTP·S3)로 전송하도록 해 두셨어도 이 링크는 따로 살아 있습니다 — 전송이 끝나기 전이나 저장소가 없을 때도 여기서 바로 틀 수 있습니다. 업체 화면이 우리 저장소에 직접 붙을 필요가 없습니다.

완료 알림 (웹훅)

폴링하지 않으셔도 됩니다. 받을 주소를 알려 주시면 작업이 끝나는 대로 POST 로 알려 드립니다 — 인코딩 · 자막 · 문제 생성 각각 다른 주소로 받을 수 있습니다.

POST (업체가 지정한 주소)
X-Jaram-Event:     job.completed          ← 또는 job.failed
X-Jaram-Delivery:  1043                   ← 전송 번호(재시도해도 같습니다)
X-Jaram-Timestamp: 1756270000
X-Jaram-Signature: sha256=<hex>

{ "event": "job.completed",
  "job": { "id": "9f3c…", "type": "transcribe", "status": "done", "progress": 100,
           "units": 72.4, "charged": 145, "finished_at": "2026-08-27T10:41:00+09:00",
           "result": { … } } }

result 는 작업 종류별 결과입니다 — 인코딩이면 result.outputs[] 에 위의 결과물 링크(download·play_url)가 그대로 들어 있어, 알림을 받는 즉시 화면에서 틀 수 있습니다.

항목내용
서명 검증HMAC-SHA256(시크릿, "<X-Jaram-Timestamp>.<본문 그대로>") 가 X-Jaram-Signature 의 sha256= 뒤와 같아야 합니다. 본문을 파싱하기 전 원문 그대로 계산하세요
중복 처리재시도로 같은 알림이 두 번 갈 수 있습니다. X-Jaram-Delivery 가 같으면 같은 알림이니 한 번만 처리하세요
우리 재시도2xx 로 답해 주시면 성공입니다. 그 밖이면 1 · 5 · 30 · 120 · 360분 뒤 최대 5회 다시 보냅니다
다시 보내지 않는 경우주소·본문 문제로 보이는 4xx(400·401·403·404·422 등)에는 재시도하지 않습니다. 고치신 뒤 알려 주시면 저희가 수동으로 다시 보내 드립니다
Retry-After보내 주시면 그 시각까지 기다립니다(하루를 넘는 값은 무시). 429·503 에 유용합니다
우리가 언제 보냈고 무엇을 받았는지(상태코드·응답 본문·시도 횟수)는 모두 기록하고 있습니다. "안 왔다" 싶을 때 문의 주시면 그 기록으로 함께 확인해 드립니다.

접근 제어 (선택)

키 하나로 다 열려 있는 게 부담스러우시면, 기능별로 잠그고 호출 주소를 좁힐 수 있습니다. 기본값은 미적용이라 아무것도 안 하시면 지금과 똑같이 동작합니다.

GET/api/v1/acl · PUT/api/v1/acl

curl -X PUT https://api.jaramai.co.kr/api/v1/acl \
  -H "X-API-Key: 발급받은키" -H "Content-Type: application/json" \
  -d '{
    "mode": "on",
    "rules": [
      { "scope": "jobs.write", "allow": true, "allowed_ips": ["203.0.113.10", "10.1.0.0/16"] },
      { "scope": "board.read", "allow": false }
    ]
  }'
mode동작
off미적용(기본). 검사하지 않습니다
audit기록만. 막지 않고 판정만 남깁니다 — 조이기 전에 무엇이 막힐지 먼저 확인하는 자리
on규칙을 넣은 기능만 검사합니다. 나머지는 열려 있습니다
strict허용한 기능만 부를 수 있습니다(화이트리스트)

GET /api/v1/acl 은 쓸 수 있는 scopes 전체 목록과 지금 호출하신 주소(your_ip)를 함께 돌려줍니다 — 허용 주소를 채울 때 그 값을 그대로 쓰시면 됩니다.

자기 발을 묶는 설정은 저장되지 않습니다. 지금 호출하는 주소에서 아무 기능도 못 부르게 되는 설정은 422 would_lock_out 으로 거절합니다. 그래도 잠기셨다면 담당자에게 알려 주세요 — 저희가 풀어 드립니다.
/api/v1/acl 자체는 이 규칙의 적용을 받지 않습니다 — 잠긴 뒤에도 설정을 되돌릴 수 있어야 하니까요.

영상 수신

POST/api/v1/ingest/{token}

업체 시스템에 새 영상이 생겼을 때 알려 주는 자리입니다. 토큰은 수신 경로마다 따로 발급하며, 이 엔드포인트는 X-API-Key 대신 그 토큰으로 신원을 확인합니다. 받은 영상은 계약한 단계(인코딩 → 자막 → 문제)까지 자동으로 진행됩니다.

이 경로는 X-API-Key 를 쓰지 않으므로 오류도 다릅니다.

HTTPerror뜻 / 대처
404unknown_token토큰이 없거나 틀림 — 발급받은 수신 주소를 확인하세요
403source_inactive그 수신 경로가 꺼져 있음 — 담당자에게 문의
422too_many_items한 번에 보낼 수 있는 건수(2,000)를 넘음 — 나눠 보내세요

재시도

연동에서 제일 자주 어긋나는 부분이라 규칙을 못박아 둡니다. 분기는 HTTP 상태와 error 코드로 하세요.

받은 응답어떻게
429재시도하세요. Retry-After 헤더가 있으면 그 초만큼 기다린 뒤에. 없으면 1분 뒤부터 점점 늘려서
500·502·503·504재시도하세요. 우리 쪽 일시 장애입니다 — 1 · 5 · 30분 식으로 간격을 늘리는 것을 권합니다
연결 실패 · 타임아웃재시도하세요. 쓰기 요청도 안전합니다(아래 참고)
400·401·403·404·422재시도하지 마세요. 같은 답이 옵니다 — message 를 읽고 고쳐서 보내야 합니다
402 insufficient_tokens·429 monthly_cap재시도해도 풀리지 않습니다. 충전·한도 조정이 필요합니다
쓰기 요청도 재시도해도 안전합니다. POST /api/v1/videos 는 external_key 가 같으면 갱신이고, POST /api/v1/jobs 는 같은 영상·같은 단계면 이미 만든 작업을 그대로 돌려줍니다 — 중복 등록도, 이중 과금도 생기지 않습니다. 다만 external_key 없이 등록하시면 회차로 판정하므로, 재시도가 걱정되시면 external_key 를 꼭 넣어 주세요.

오류

오류는 두 칸으로 옵니다 — error(분기용 코드)와 message(사람이 읽을 설명). 분기는 HTTP 상태가 아니라 error 로 하세요 — 같은 404 라도 뜻이 다릅니다. 단 하나의 예외가 분당 호출 제한(429)으로, 이때는 error 없이 message 만 옵니다.

{ "error": "subtitle_not_ready", "message": "자막이 아직 준비되지 않았습니다." }
HTTPerror뜻 / 대처
401unauthorized키가 없거나 틀림 — X-API-Key 확인
403inactive계약이 활성 상태가 아님
403not_licensed그 자료에 해당하는 상품 미계약 — 계약 범위 확인
404video_not_found없는 영상이거나 업체 영상이 아님
404category_not_found없는 분류이거나 업체 분류가 아님
404subtitle_not_ready실패가 아니라 아직 — 그대로 두고 나중에 다시 부르세요
404summary_not_ready실패가 아니라 아직 — 기다리시면 됩니다
404summary_failed요약을 만들지 못함 — 알려 주시면 다시 만들어 드립니다
402insufficient_tokens토큰 잔액 부족 — 재시도해도 풀리지 않습니다, 충전이 필요합니다
422bad_cursor커서가 깨짐 — 앞선 응답의 next_cursor 를 그대로 보내세요
422invalid_payload파라미터가 틀림 — message 를 그대로 읽으면 됩니다
429— (없음)분당 호출 제한 — 이때만 error 칸이 없고 message 만 옵니다. Retry-After 헤더의 초만큼 기다린 뒤 재시도
429rate_limit토큰 한도 초과 — 한도가 걸린 기간이 지나야 풀립니다
429monthly_cap월 한도 초과 — 재시도해도 풀리지 않습니다, 담당자에게 문의
502upstream_failedAI 호출 실패 — 우리 쪽 일시 장애입니다, 잠시 뒤 재시도
403acl_denied접근 설정에서 막힘 — scope 칸에 어떤 기능인지 담깁니다. /api/v1/acl 로 설정 확인
404job_not_found없는 작업이거나 업체 작업이 아님
404storage_not_found없는 저장소 설정 번호(storage_profile_id)
409subtitle_not_ready문제 생성을 시켰는데 자막이 아직 없음 — 자막부터 끝난 뒤 다시 요청
409not_startable지금은 시작할 수 없음 — 잔액·계약·동시 처리 한도를 확인하세요
422category_not_leaf영상은 소분류에 매답니다 — 가장 아래 분류를 고르세요
422category_too_deep분류를 소(3단) 아래로 만들려 함 — 대 > 중 > 소 까지만 됩니다
409category_in_use하위 분류·영상·과정이 매달려 삭제 불가 — in_use 의 개수를 비운 뒤 지우세요
422source_unavailable원본이 그 자리에 없음 — items[] 에 항목별 사유가 담깁니다. 한 건도 등록되지 않았습니다
422storage_requiredpath 로 보내셨는데 저장소를 고를 수 없음 — storages[] 에서 골라 storage_profile_id 로 지정
422source_missing작업을 시킨 영상에 원본 위치가 없음 — 먼저 url·path 를 등록
422would_lock_out그 접근 설정을 적용하면 스스로 잠깁니다 — 저장하지 않았습니다
422bad_ip허용 주소 형식 오류 — 1.2.3.4 또는 10.0.0.0/8
422duplicate_scope접근 설정에 같은 scope 를 두 번 보냄
subtitle_not_ready·summary_not_ready 를 실패로 처리하지 마세요. 아직 만드는 중이라는 뜻입니다. 진행 상태는 /api/v1/subtitles 로 확인하시고, correction 이 done 이 된 뒤에 본문을 받아 가시면 헛걸음이 없습니다.

문의는 담당자에게 주세요. 이 문서는 실제 서비스 중인 규격과 함께 갱신됩니다.

바뀐 점

날짜내용
2026-09-15 · 인코딩 결과물 HLS/DASH 스트리밍 묶음 — outputs[] 에 kind: "package"(hls_url·dash_url), 결과물 전송은 관리번호/stream/ 폴더
· POST /api/v1/jobs 에 format(mp4·hls·dash) · aspect(결과물 비율) · fit(여백 두기/꽉 채우기) — 생략하면 업체 설정·원본 비율
· 결과물 주소(play_url·hls_url)의 호스트는 업체 도메인으로 바꿔 불러도 됩니다(서명은 경로·만료·작업번호에만)
2026-09-14· GET /api/v1/jobs/{id}/output/{filename} — 인코딩 결과물 받기(download·play_url), 작업·영상 조회와 완료 알림에 outputs[]
2026-09-11· GET /api/v1/me(내 계약) · POST /api/v1/board(게시물 등록)

이 문서와 같은 내용의 사본이 한 벌 더 있습니다(아티팩트). 둘 중 어느 쪽을 보셔도 같습니다.