← 바른 글
API

Run 바른 글 from your own code

바른 글 (Bareun Geul) is a Korean spelling and spacing checker. Its one metered task, proof, is available over HTTP. It takes a Korean text as the page has read it - the kind of writing (genre), the register (style: 격식체 or 비격식체), the text split into numbered sentences (s1, s2 ...) and the facts the page found for free (the counts, a length limit when one is set, and every slip its rule list is sure of) - and returns a verdict (clean, polish or fix), one entry per correction (the sentence, the exact wrong span, the right form, its kind, its level and a one-line reason) and a note. The run is on gpt-terra and metered per run; /estimate is free.

The division of labour first: the page counts the text and marks the slips that need no context for free; the run reads every sentence for what needs context - particles, endings, auxiliary verbs, dependent nouns, punctuation. The page reads up to 20,000 characters and sends at most 160 sentences a run. After the reply it keeps a correction only when its wrong span is really in that sentence, it does not overlap another kept correction, it changes something, and its kind and level are known ones. Then it checks that every certain slip its own rules found is fixed, that the verdict follows the levels, that no correction brings in a digit or changes an English word, and that no reason or note quotes a number the input does not have. Kept corrections are marked in place on the text and applied to a corrected copy (copy, .txt, .md or .json). Your code can do the same with the engine files the page loads (see "Build the input yourself"). Not affiliated with the National Institute of Korean Language (국립국어원); results are for reference.

Base URL and the response shape

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }

The request example throughout is the cover sample's exact wire input (the first worked example below), saved as input.json. The token is minted for this app (the guest call takes {"slug":"bareun-geul"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required key (task, genre, style, sentences, facts) is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the proof run is metered and needs a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field, the input and the rules

Every run sends task: proof (the one task is proof); a missing or different one is still answered as proof. Every value is a string. What each key holds, in the words of the system prompt (Korean), with the first sample's value (its first line where it has several):

KeyRequiredWhat it holds
taskyes항상 proof. 없거나 다르면 이 작업으로 답하고 "task": "proof"로 적습니다.
genreyes글의 종류(자기소개서, 업무 이메일, 보고서, 기획서, 블로그, 공지, 기타). First sample: 자기소개서
styleyes문체: 격식체 또는 비격식체. First sample: 격식체
sentencesyes한 줄에 한 문장: s1. 문장 형식. 문장은 붙여 넣은 그대로입니다. First sample: s1. 저는 대학 시절 학생회 기획부에서 3년 동안 활동하며 행사를 준비했습니다. ...
factsyes페이지가 센 글자 수와 제한, 그리고 규칙 목록이 찾은 오류(한 줄에 하나, [확실] 또는 [권장], 문장 번호, 틀린 부분, 고친 부분, 근거). First sample: 글자 수: 공백 포함 290자, 공백 제외 224자, 7문장. ...
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

The paste has five keys (GeulKit.form): text, genre, style, limit (a number, or empty) and unit. genre and style are codes the page turns into the Korean words the run reads:

genreSent as
cover자기소개서
email업무 이메일
report보고서, 기획서
post블로그, 공지
other기타
styleSent as
formal격식체
casual비격식체

An unknown genre reads as other, an unknown style as formal. Counting (GeulKit.counts): characters with spaces and without (a line break is not counted as a character), bytes with every non-ASCII character (Hangul, Hanja) as 2 and ASCII as 1 - the way many job-application forms count - and UTF-8 bytes, plus Hangul syllables, line breaks and paragraphs. A limit is a number in one of three units:

unitCounted as
chars자 (공백 포함)
nospace자 (공백 제외)
bytes바이트 (한글 2)

Sentences (GeulKit.sentences): a sentence ends at ., !, ? or … (a run of them, and any closing quote or bracket) before a space or the end, or at a line break; each keeps its offsets in the text so a correction can be put back exactly where it belongs. The text is read up to 20,000 characters and the first 160 sentences are sent; the page warns about the rest. A text with fewer than 10 Hangul syllables is not sent at all.

The slips the page marks for free

Each sentence is checked against a rule list (GeulKit.RULES, GeulKit.checkSentence). A rule is on this list only if the wrong form is wrong in every sentence it can appear in; everything that needs context is left to the run. Overlapping hits keep the first rule's. A fix hit goes into facts tagged [확실] and the run must include it; a check hit is tagged [권장].

CodeKindLevelWrong → rightReason (as the page writes it)
word1맞춤법fix (확실)몇\s?일 → 며칠'며칠'이 표준어입니다. '몇 일'로 쓰지 않습니다.
word2맞춤법fix (확실)금새 → 금세'지금 바로'라는 뜻은 '금시에'가 줄어든 '금세'입니다.
word3맞춤법fix (확실)어떻해 → 어떡해'어떻게 해'가 줄면 '어떡해'입니다.
word4맞춤법fix (확실)어떡게 → 어떻게방법을 묻는 말은 '어떻게'입니다.
word5맞춤법fix (확실)일일히 → 일일이'일일이'로 씁니다(부사 '-이').
word6맞춤법fix (확실)깨끗히 → 깨끗이'-하다' 앞이 'ㅅ' 받침이면 '-이'로 적어 '깨끗이'입니다.
word7맞춤법fix (확실)곰곰히 → 곰곰이'곰곰이'로 씁니다.
word8맞춤법fix (확실)틈틈히 → 틈틈이같은 말이 겹친 부사는 '-이'로 적어 '틈틈이'입니다.
word9맞춤법fix (확실)번번히 → 번번이같은 말이 겹친 부사는 '-이'로 적어 '번번이'입니다.
word10맞춤법fix (확실)일찌기 → 일찍이'일찍이'로 씁니다.
word11맞춤법fix (확실)오랫만 → 오랜만'오래간만'이 줄어든 말이라 '오랜만'입니다.
word12맞춤법fix (확실)설겆이 → 설거지'설거지'가 표준어입니다.
word13맞춤법fix (확실)희안하 → 희한하'희한(稀罕)하다'가 맞습니다.
word14맞춤법fix (확실)어의없 → 어이없'어처구니없다'는 뜻은 '어이없다'입니다.
word15맞춤법fix (확실)역활 → 역할'역할(役割)'이 맞습니다.
word16맞춤법fix (확실)무릎쓰 → 무릅쓰'어려움을 참고 견디다'는 '무릅쓰다'입니다.
word17맞춤법fix (확실)내노라하 → 내로라하'내로라하다'가 맞습니다.
word18맞춤법fix (확실)승락 → 승낙'승낙(承諾)'이 맞습니다.
word19맞춤법fix (확실)갯수 → 개수한자어 '개수(個數)'에는 사이시옷을 넣지 않습니다.
word20맞춤법fix (확실)댓가 → 대가한자어 '대가(代價)'에는 사이시옷을 넣지 않습니다.
word21맞춤법fix (확실)촛점 → 초점한자어 '초점(焦點)'에는 사이시옷을 넣지 않습니다.
word22맞춤법fix (확실)웬지 → 왠지'왜인지'가 줄면 '왠지'입니다.
word23맞춤법fix (확실)뵈요 → 봬요'뵈어요'가 줄면 '봬요'입니다.
word24맞춤법fix (확실)뵜 → 뵀'뵈었-'이 줄면 '뵀-'입니다.
word25띄어쓰기fix (확실)안되요 → 안 돼요'되어요'의 준말은 '돼요'이고, 부정의 '안'은 띄어 씁니다.
word26맞춤법fix (확실)됬 → 됐'되었-'의 준말은 '됐-'입니다.
word27맞춤법fix (확실)되요 → 돼요'되어요'의 준말은 '돼요'입니다.
word28맞춤법fix (확실)되서 → 돼서'되어서'의 준말은 '돼서'입니다.
word29맞춤법fix (확실)되야 → 돼야'되어야'의 준말은 '돼야'입니다.
word30맞춤법fix (확실)되도(?!록) → 돼도'되어도'의 준말은 '돼도'입니다.
word31맞춤법fix (확실)읍니다 → 습니다'-읍니다'는 옛 표기이고 지금은 '-습니다'로 씁니다.
waen맞춤법fix (확실)왠(?!지) → 웬'어찌 된'이라는 뜻은 '웬'입니다. '왠'은 '왠지'에만 씁니다.
lkke맞춤법fix (확실)-ㄹ께 → -ㄹ게 (a syllable ending in ㄹ, then 께 at the end of the word (할께, 갈께요); 들께 is left alone)'-ㄹ게'는 소리가 [께]로 나도 '게'로 적습니다.
ji_an맞춤법fix (확실)-지 안- → -지 않- (the negation after -지 written 안 (하지 안다, 쉽지 안아))'-지' 뒤에 오는 부정은 '않다'(아니하다)입니다.
an_verb맞춤법fix (확실)않 + verb → 안 + verb (않 standing alone before a verb, where the adverb 안 belongs)용언 앞에서 부정하는 부사는 '안'(아니)입니다.
su띄어쓰기fix (확실)-ㄹ수 있/없 → -ㄹ 수 있/없 (수 after a syllable ending in ㄹ, before 있 or 없 (할수 있다); 할수록 stays together)'수'는 의존 명사라서 앞말과 띄어 씁니다.
geot띄어쓰기fix (확실)-는것, -던것, -ㄹ것 → -는 것 (것 glued to 는, 던 or a syllable ending in ㄹ (이것, 그것, 저것 and the like stay together))'것'은 의존 명사라서 앞말과 띄어 씁니다.
marks문장 부호check (권장)!!, ??, !? → !, ? (a doubled exclamation or question mark (a check, not a certain slip))느낌표나 물음표는 하나만 씁니다(특히 격식 있는 글에서).

GeulKit.buildInput writes sentences (one line per sentence, s1. and the sentence exactly as pasted) and facts (GeulKit.factLines): the counts and the number of sentences, the limit with what is used and what is left or over, one line per rule hit - [확실] or [권장], the sentence, "wrong" -> "right" and the reason - or a line saying the rule list found nothing, and how many sentences run past 80 characters.

The model's rules

As the system prompt states them (in Korean), in order:

  1. corrections - 고칠 곳마다 하나씩, 문장 번호 순서대로: sentence(그 문장의 번호), wrong, right, kind, level, why.
    • wrong은 그 문장 안에 그대로 있는, 오류를 담은 가장 짧은 부분입니다. 띄어쓰기까지 한 글자도 다르지 않게 옮겨 적습니다. 같은 글자들이 그 문장에 두 번 이상 나오면 앞뒤 글자를 붙여 하나로 정해지게 합니다.
    • right는 wrong 자리에 그대로 들어갈 고친 표기입니다. 단어나 구 하나만 고치고 문장을 새로 쓰지 않습니다. 고유 명사, 숫자, 영어 낱말, 뜻은 그대로 둡니다.
    • kind는 맞춤법, 띄어쓰기, 문법, 문장 부호 가운데 하나입니다. 띄어쓰기만 다른 교정은 띄어쓰기입니다.
    • level은 확실(규정에 어긋남이 분명함), 권장(규정이 더 나은 표기를 정함, 예: 격식체에서 보조 용언 띄어 쓰기, 영어식 쉼표), 제안(규정 위반은 아니지만 읽기 쉽게 다듬기) 가운데 하나입니다. 제안은 글 전체에서 셋까지만 둡니다.
    • why는 한 문장, 50자 이내로 근거를 밝힙니다(예: 의존 명사, 준말, 보조 용언, 조사와 받침).
  2. facts의 [확실] 줄은 모두 corrections에 넣습니다(같은 문장, 같은 고침).
  3. 비격식체에서는 입말에서 자연스러운 줄임(근데, 거, 그치만 같은 것)을 고치지 않습니다. 격식체에서는 규정을 엄격히 적용합니다.
  4. 글이 규정에 맞으면 고치지 않습니다. 고칠 것이 없으면 corrections는 []입니다.
  5. verdict - 확실이 하나라도 있으면 fix, 권장이나 제안만 있으면 polish, 고칠 것이 없으면 clean.
  6. note - 한 문장, 60자 이내: 글 전체의 상태와 가장 먼저 고칠 것.

And the house rules:

The reply

One JSON object, in the shape the system prompt asks for:

{
  "task": "proof",
  "verdict": "clean | polish | fix",
  "corrections": [ { "sentence": "s1", "wrong": "틀린 부분 그대로", "right": "고친 표기", "kind": "맞춤법 | 띄어쓰기 | 문법 | 문장 부호", "level": "확실 | 권장 | 제안", "why": "근거 한 문장" } ],
  "note": "한 문장"
}

The page reads the reply with ReplyKit.parseReply (code fences are stripped, and a reply cut short is closed so what arrived can still be read) and ReplyKit.normalize: the verdict matched in upper or lower letters (anything other than clean, polish or fix reads as unknown), a sentence id such as 2 read as s2, wrong and right kept exactly as written (their spaces matter), and an entry with no sentence or no wrong dropped.

ReplyKit.trusted(reply, built) decides which corrections the page keeps. One is dropped when its sentence was not sent, when its wrong span is not in that sentence character for character, when right equals wrong, when its level is not one of 확실, 권장 or 제안 or its kind not one of 맞춤법, 띄어쓰기, 문법 or 문장 부호, or when it overlaps a correction already kept in the same sentence. A span that occurs twice in its sentence is placed at the first occurrence.

ReplyKit.check(reply, built) lists, under the page's check: a missing verdict, or one that does not follow the kept levels (fix when any is 확실, polish when there are only 권장 or 제안, clean when there are none - ReplyKit.expectVerdict); every dropped correction; every [확실] slip of the page's own that is still in the corrected sentence; a correction that brings in a digit or changes an English word; and in the note and every why, a number that is not in the input, backticks or an input field name. These are warnings and make the status flagged. As notes only: a correction whose length changes by more than 15 characters (a rewrite rather than a fix), a spacing-only change not called 띄어쓰기, a span placed at its first of several occurrences, and more than three 제안. Otherwise the status is kept. The verdict on the page reads "고칠 곳 없음" (clean), "다듬을 곳 있음" (polish), "고칠 곳 있음" (fix).

What the page builds from it: GeulView.marked draws the text with every kept correction in place - the wrong span struck through, the right one beside it, a number linking to the reason - its level a word on the mark's title (확실, 권장, 제안) and a colour; ReplyKit.corrected(built, reply) is the text with every kept correction applied (GeulKit.apply, from the last to the first so the offsets hold); ReplyKit.md and ReplyKit.json write the corrections as a table and the corrected text. Nothing is sent anywhere else: copy or download the corrected text yourself.

Worked examples - every sample

These are the free samples on the page: the text as pasted, how the page counts it and the slips its rule list marks, the exact input it sends, the reply that replays for free, and what the page does with it - the corrections it keeps, the corrected text, the verdict and its check. Together they cover the verdicts fix, polish and clean.

자기소개서 한 문항

genre cover (자기소개서), style formal (격식체), a limit of 500자 (공백 포함): 290 used, 210 left.

The text as pasted:

저는 대학 시절 학생회 기획부에서 3년 동안 활동하며 행사를 준비했습니다. 처음에는 50명 규모의 작은 행사였지만, 몇일 만에 참가 신청이 200명을 넘으면서 계획을 금새 바꿔야 했습니다. 그때 저는 팀원들과 역활을 다시 나누고, 매일 아침 진행 상황을 공유하는것을 제안했습니다. 덕분에 행사는 무사히 끝났고, 설문에서 만족도 92%라는 결과를 얻었습니다.

이 경험을 통해 일정이 바뀌어도 침착하게 대응할수 있다는 자신감을 얻었습니다. 입사 후에도 맡은 일을 끝까지 책임지는 사람이 되겠습니다. 꼭 함께 일하고 싶읍니다.

The page reads 7 sentences: 290 characters with spaces, 224 without, 497 bytes with Hangul as 2, 702 UTF-8 bytes. Its rule list marks 6 slips:

SentenceWrongRightKindLevelReason
s2몇일며칠맞춤법fix (확실)'며칠'이 표준어입니다. '몇 일'로 쓰지 않습니다.
s2금새금세맞춤법fix (확실)'지금 바로'라는 뜻은 '금시에'가 줄어든 '금세'입니다.
s3역활역할맞춤법fix (확실)'역할(役割)'이 맞습니다.
s3는것는 것띄어쓰기fix (확실)'것'은 의존 명사라서 앞말과 띄어 씁니다.
s5할수 있할 수 있띄어쓰기fix (확실)'수'는 의존 명사라서 앞말과 띄어 씁니다.
s7읍니다습니다맞춤법fix (확실)'-읍니다'는 옛 표기이고 지금은 '-습니다'로 씁니다.

Input:

{
 "task": "proof",
 "genre": "자기소개서",
 "style": "격식체",
 "sentences": "s1. 저는 대학 시절 학생회 기획부에서 3년 동안 활동하며 행사를 준비했습니다.\ns2. 처음에는 50명 규모의 작은 행사였지만, 몇일 만에 참가 신청이 200명을 넘으면서 계획을 금새 바꿔야 했습니다.\ns3. 그때 저는 팀원들과 역활을 다시 나누고, 매일 아침 진행 상황을 공유하는것을 제안했습니다.\ns4. 덕분에 행사는 무사히 끝났고, 설문에서 만족도 92%라는 결과를 얻었습니다.\ns5. 이 경험을 통해 일정이 바뀌어도 침착하게 대응할수 있다는 자신감을 얻었습니다.\ns6. 입사 후에도 맡은 일을 끝까지 책임지는 사람이 되겠습니다.\ns7. 꼭 함께 일하고 싶읍니다.",
 "facts": "글자 수: 공백 포함 290자, 공백 제외 224자, 7문장.\n제한: 500자 (공백 포함) - 지금 290, 210 남음.\n[확실] s2 \"몇일\" -> \"며칠\": '며칠'이 표준어입니다. '몇 일'로 쓰지 않습니다.\n[확실] s2 \"금새\" -> \"금세\": '지금 바로'라는 뜻은 '금시에'가 줄어든 '금세'입니다.\n[확실] s3 \"역활\" -> \"역할\": '역할(役割)'이 맞습니다.\n[확실] s3 \"는것\" -> \"는 것\": '것'은 의존 명사라서 앞말과 띄어 씁니다.\n[확실] s5 \"할수 있\" -> \"할 수 있\": '수'는 의존 명사라서 앞말과 띄어 씁니다.\n[확실] s7 \"읍니다\" -> \"습니다\": '-읍니다'는 옛 표기이고 지금은 '-습니다'로 씁니다."
}

Reply:

{
 "task": "proof",
 "verdict": "fix",
 "corrections": [
  {
   "sentence": "s2",
   "wrong": "몇일",
   "right": "며칠",
   "kind": "맞춤법",
   "level": "확실",
   "why": "며칠이 표준어이며 몇 일로 적지 않음"
  },
  {
   "sentence": "s2",
   "wrong": "금새",
   "right": "금세",
   "kind": "맞춤법",
   "level": "확실",
   "why": "금시에가 줄어든 금세가 바른 표기임"
  },
  {
   "sentence": "s3",
   "wrong": "역활",
   "right": "역할",
   "kind": "맞춤법",
   "level": "확실",
   "why": "역할이 바른 한자어 표기임"
  },
  {
   "sentence": "s3",
   "wrong": "는것",
   "right": "는 것",
   "kind": "띄어쓰기",
   "level": "확실",
   "why": "것은 의존 명사라서 앞말과 띄어 씀"
  },
  {
   "sentence": "s5",
   "wrong": "할수 있",
   "right": "할 수 있",
   "kind": "띄어쓰기",
   "level": "확실",
   "why": "수는 의존 명사라서 앞말과 띄어 씀"
  },
  {
   "sentence": "s7",
   "wrong": "읍니다",
   "right": "습니다",
   "kind": "맞춤법",
   "level": "확실",
   "why": "읍니다는 옛 표기이고 지금은 습니다로 씀"
  }
 ],
 "note": "몇일, 금새, 역활 등 맞춤법과 띄어쓰기 오류를 먼저 고치세요."
}

The corrections the page keeps (ReplyKit.trusted): 6 of 6 corrections.

SentenceWrongRightKindLevelReason
s2몇일며칠맞춤법확실며칠이 표준어이며 몇 일로 적지 않음
s2금새금세맞춤법확실금시에가 줄어든 금세가 바른 표기임
s3역활역할맞춤법확실역할이 바른 한자어 표기임
s3는것는 것띄어쓰기확실것은 의존 명사라서 앞말과 띄어 씀
s5할수 있할 수 있띄어쓰기확실수는 의존 명사라서 앞말과 띄어 씀
s7읍니다습니다맞춤법확실읍니다는 옛 표기이고 지금은 습니다로 씀

The corrected text (ReplyKit.corrected):

저는 대학 시절 학생회 기획부에서 3년 동안 활동하며 행사를 준비했습니다. 처음에는 50명 규모의 작은 행사였지만, 며칠 만에 참가 신청이 200명을 넘으면서 계획을 금세 바꿔야 했습니다. 그때 저는 팀원들과 역할을 다시 나누고, 매일 아침 진행 상황을 공유하는 것을 제안했습니다. 덕분에 행사는 무사히 끝났고, 설문에서 만족도 92%라는 결과를 얻었습니다.

이 경험을 통해 일정이 바뀌어도 침착하게 대응할 수 있다는 자신감을 얻었습니다. 입사 후에도 맡은 일을 끝까지 책임지는 사람이 되겠습니다. 꼭 함께 일하고 싶습니다.

The verdict: fix - "고칠 곳 있음"; it follows the kept levels.

The note: "몇일, 금새, 역활 등 맞춤법과 띄어쓰기 오류를 먼저 고치세요."

The page's check (ReplyKit.check): kept.

No notes.

업무 이메일

genre email (업무 이메일), style formal (격식체), no limit.

The text as pasted:

안녕하세요, 이영희 팀장님.
마케팅팀 홍길동입니다.
지난주에 말씀드린 3분기 캠페인 결과 보고서를 첨부해 드립니다.
검토해주시면 다음 주 회의 전까지 수정하겠습니다.
만약 일정이 어려우시면, 편하신 시간을 알려 주세요.
늘 도와주셔서 감사합니다!!

The page reads 6 sentences: 133 characters with spaces, 110 without, 238 bytes with Hangul as 2, 338 UTF-8 bytes. Its rule list marks 1 slip:

SentenceWrongRightKindLevelReason
s6!!!문장 부호check (권장)느낌표나 물음표는 하나만 씁니다(특히 격식 있는 글에서).

Input:

{
 "task": "proof",
 "genre": "업무 이메일",
 "style": "격식체",
 "sentences": "s1. 안녕하세요, 이영희 팀장님.\ns2. 마케팅팀 홍길동입니다.\ns3. 지난주에 말씀드린 3분기 캠페인 결과 보고서를 첨부해 드립니다.\ns4. 검토해주시면 다음 주 회의 전까지 수정하겠습니다.\ns5. 만약 일정이 어려우시면, 편하신 시간을 알려 주세요.\ns6. 늘 도와주셔서 감사합니다!!",
 "facts": "글자 수: 공백 포함 133자, 공백 제외 110자, 6문장.\n[권장] s6 \"!!\" -> \"!\": 느낌표나 물음표는 하나만 씁니다(특히 격식 있는 글에서)."
}

Reply:

{
 "task": "proof",
 "verdict": "polish",
 "corrections": [
  {
   "sentence": "s4",
   "wrong": "해주",
   "right": "해 주",
   "kind": "띄어쓰기",
   "level": "권장",
   "why": "보조 동사 주다는 격식 있는 글에서 띄어 씀이 바람직합니다"
  },
  {
   "sentence": "s6",
   "wrong": "!!",
   "right": "!",
   "kind": "문장 부호",
   "level": "권장",
   "why": "격식 있는 글에서는 느낌표를 하나만 쓰는 것이 바람직합니다"
  }
 ],
 "note": "전반적으로 격식에 맞으며, 해주시면 띄어쓰기와 느낌표 중복만 다듬으면 좋습니다."
}

The corrections the page keeps (ReplyKit.trusted): 2 of 2 corrections.

SentenceWrongRightKindLevelReason
s4해주해 주띄어쓰기권장보조 동사 주다는 격식 있는 글에서 띄어 씀이 바람직합니다
s6!!!문장 부호권장격식 있는 글에서는 느낌표를 하나만 쓰는 것이 바람직합니다

The corrected text (ReplyKit.corrected):

안녕하세요, 이영희 팀장님.
마케팅팀 홍길동입니다.
지난주에 말씀드린 3분기 캠페인 결과 보고서를 첨부해 드립니다.
검토해 주시면 다음 주 회의 전까지 수정하겠습니다.
만약 일정이 어려우시면, 편하신 시간을 알려 주세요.
늘 도와주셔서 감사합니다!

The verdict: polish - "다듬을 곳 있음"; it follows the kept levels.

The note: "전반적으로 격식에 맞으며, 해주시면 띄어쓰기와 느낌표 중복만 다듬으면 좋습니다."

The page's check (ReplyKit.check): kept.

No notes.

도서관 공지

genre post (블로그, 공지), style formal (격식체), no limit.

The text as pasted:

도서관 이용 시간 변경 안내
다음 달 1일부터 평일 이용 시간이 오전 9시부터 오후 9시까지로 바뀝니다.
주말과 공휴일에는 오전 10시부터 오후 5시까지 이용할 수 있습니다.
변경된 시간은 도서관 누리집에서도 확인할 수 있습니다.
이용에 참고하시기 바랍니다.

The page reads 5 sentences: 140 characters with spaces, 110 without, 244 bytes with Hangul as 2, 344 UTF-8 bytes. Its rule list marks nothing; whatever needs fixing needs context.

Input:

{
 "task": "proof",
 "genre": "블로그, 공지",
 "style": "격식체",
 "sentences": "s1. 도서관 이용 시간 변경 안내\ns2. 다음 달 1일부터 평일 이용 시간이 오전 9시부터 오후 9시까지로 바뀝니다.\ns3. 주말과 공휴일에는 오전 10시부터 오후 5시까지 이용할 수 있습니다.\ns4. 변경된 시간은 도서관 누리집에서도 확인할 수 있습니다.\ns5. 이용에 참고하시기 바랍니다.",
 "facts": "글자 수: 공백 포함 140자, 공백 제외 110자, 5문장.\n규칙 목록으로 확실히 잡히는 오류는 없습니다."
}

Reply:

{
 "task": "proof",
 "verdict": "clean",
 "corrections": [],
 "note": "문법과 맞춤법, 띄어쓰기 모두 규정에 맞아 고칠 곳이 없습니다."
}

The reply has no corrections, so the text stands as written.

The verdict: clean - "고칠 곳 없음"; it follows the kept levels.

The note: "문법과 맞춤법, 띄어쓰기 모두 규정에 맞아 고칠 곳이 없습니다."

The page's check (ReplyKit.check): kept.

No notes.

Build the input yourself

Load the engine in node (global.window = global; require("./geulkit.js"); require("./replykit.js")), then:

const K = GeulKit, R = ReplyKit;
const form = K.form({ text: "회의 자료는 몇일 뒤에 보내 드리겠습니다. 미리 검토해 주시면 일정을 맞출수 있습니다.",
  genre: "email", style: "formal", limit: "", unit: "chars" });
// form.sentences: [{id, text, start, end, finds}] - s1, s2 ... with their offsets in form.text
// form.findings: [{code, kind, level, sid, start, end, wrong, right, why}] - the rule list's sure slips (level fix or check)
// form.counts: {chars, nospace, bytes, utf8, hangul, newlines, paragraphs}; form.limit, form.used, form.over
const built = K.buildInput(form);     // built.ok is false, with built.note, when there is too little Korean text
// built.warn: what the page warns about (a clipped text, sentences past 160)
const wire = K.toWire(built.input);   // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output));
// R.check(reply, built)        // {status: kept or flagged, notes}
// R.trusted(reply, built)      // {edits, dropped}
// R.corrected(built, reply)    // the text with every kept correction applied

That gives this wire input - two sentences, and two slips the rule list is sure of, which the run must include:

{
 "task": "proof",
 "genre": "업무 이메일",
 "style": "격식체",
 "sentences": "s1. 회의 자료는 몇일 뒤에 보내 드리겠습니다.\ns2. 미리 검토해 주시면 일정을 맞출수 있습니다.",
 "facts": "글자 수: 공백 포함 48자, 공백 제외 37자, 2문장.\n[확실] s1 \"몇일\" -> \"며칠\": '며칠'이 표준어입니다. '몇 일'로 쓰지 않습니다.\n[확실] s2 \"출수 있\" -> \"출 수 있\": '수'는 의존 명사라서 앞말과 띄어 씁니다."
}

Paste keys: text (the text as written), genre (cover, email, report, post or other), style (formal or casual), limit (a number, or empty for none) and unit (chars, nospace or bytes). The engine sends nothing anywhere: it only reads the text you give it.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://bareun-geul.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"bareun-geul"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra; the run is metered per run.

# input.json: the run input object - {"task": "proof", "genre": "자기소개서", "style": "격식체", "sentences": "s1. 저는 대학 시절 학생회 기획부에서 3년 동안 활동하며 행사를 준비했습니다.\ns2. 처음에는 50명 규모의 작은 행사였지만, 몇일 만에 참가 신청이 200명을 넘으면서 계획을 금새 바꿔야 했습니다.\ns3. 그때 저는 팀원들과 역활을 다시 나누고, 매일 아침 진행 상황을 공유하는것을 제안했습니다.\ns4. 덕분에 행사는 무사히 끝났고, 설문에서 만족도 92%라는 결과를 얻었습니다.\ns5. 이 경험을 통해 일정이 바뀌어도 침착하게 대응할수 있다는 자신감을 얻었습니다.\ns6. 입사 후에도 맡은 일을 끝까지 책임지는 사람이 되겠습니다.\ns7. 꼭 함께 일하고 싶읍니다.", "facts": "글자 수: 공백 포함 290자, 공백 제외 224자, 7문장.\n제한: 500자 (공백 포함) - 지금 290, 210 남음.\n[확실] s2 \"몇일\" -> \"며칠\": '며칠'이 표준어입니다. '몇 일'로 쓰지 않습니다.\n[확실] s2 \"금새\" -> \"금세\": '지금 바로'라는 뜻은 '금시에'가 줄어든 '금세'입니다.\n[확실] s3 \"역활\" -> \"역할\": '역할(役割)'이 맞습니다.\n[확실] s3 \"는것\" -> \"는 것\": '것'은 의존 명사라서 앞말과 띄어 씁니다.\n[확실] s5 \"할수 있\" -> \"할 수 있\": '수'는 의존 명사라서 앞말과 띄어 씁니다.\n[확실] s7 \"읍니다\" -> \"습니다\": '-읍니다'는 옛 표기이고 지금은 '-습니다'로 씁니다."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses bareun-geul:<task>:<inputHash>:a<attempt> (the one task is proof), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="bareun-geul:proof:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.verdict, .note' reply.json
jq -r '.corrections[]? | "\(.sentence) \(.wrong) -> \(.right) [\(.kind), \(.level)] \(.why)"' reply.json   # one per correction

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: bareun-geul:proof:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json