← Visa Copy
API

Run Visa Copy from your own code

Visa Copy translates a Chinese official document - a bank deposit certificate, an employment, income or retirement certificate, a business licence, a household register page and the like - into English for a visa file. Its one metered task, translate, is available over HTTP. It takes the document as the page has read it - the English document type (doc_type), the date style (date_style), the original one numbered line at a time (L1, L2 ...) and the facts the page read for certain for free (every date and amount with its English form, every ID, account and phone number, every name in passport-style pinyin, the standard English for the terms it knows, and its own warnings) - and returns an English title, one English entry per line, one note per real problem, a verdict (ready or check) and a note. The run is on gpt-terra and metered per run; /estimate is free.

The division of labour first: the page reads the document for free - dates, amounts, ID and account numbers, names, known terms - and the run writes the English. The page reads up to 8,000 characters and sends at most 120 lines a run. After the reply it checks that every line sent is translated exactly once and in order, that every date and amount it read appears in its English form in the English of its own line (with the English words for an amount written in capital numerals), that every ID, account and phone number is copied digit for digit, that every name uses its spelling, that no line brings in a number the original line does not have, that no Chinese stays outside parentheses, that every warning it raised is carried as a note, and that the verdict follows the notes. The translation is shown beside the original, line by line, and prints as a two-page packet - the original, then the English with a translator's statement - or is copied or downloaded as .txt, .md or .json. Your code can do the same with the engine files the page loads (see "Build the input yourself"). Not a certified translation service, not legal or immigration advice, and not affiliated with any embassy or visa office (or with Visa Inc.); the translator's statement is yours to sign, and some visa offices ask for a certified or sworn translation instead.

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 deposit 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":"visa-copy"}), 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, doc_type, date_style, lines, 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 translate 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: translate (the one task is translate); a missing or different one is still answered as translate. Every value is a string. What each key holds, in the words of the system prompt, with the first sample's value (its first line where it has several):

KeyRequiredWhat it holds
taskyesalways translate.
doc_typeyesthe English document type the page recognised, or unknown. First sample: Certificate of Deposit
date_styleyesD Month YYYY (5 March 2024) or Month D, YYYY (March 5, 2024). First sample: D Month YYYY
linesyesthe original, one line each, numbered L1., L2., ... First sample: L1. 存款证明 ...
factsyeswhat the page read for certain: every date and amount with its English form, every ID, account and phone number, every name in passport-style pinyin, the standard English for known terms, and the page's own warnings. First sample: Document type: 存款证明 -> Certificate of Deposit. ...
retry_notenoOnly on a reformat retry: why the previous reply was unusable.

The paste has four keys (DocKit.form): text (the document's Chinese text, line by line as printed), dest (the visa the translation is for), spellings (optional lines such as 吴正业 = WU Zhengye: the spelling on the passport, which wins over the page's pinyin) and translator (a name for the translator's statement on the print; it is never sent). dest sets only the date style; an unknown one reads as other:

destVisa fordate_style
ukUnited KingdomD Month YYYY
usUnited StatesMonth D, YYYY
caCanadaD Month YYYY
auAustraliaD Month YYYY
schengenSchengen areaD Month YYYY
otherOtherD Month YYYY

What the page reads for free

Lines. Windows line breaks read as plain ones, an ideographic space becomes a space, every line has its runs of white space squeezed to one, and empty lines are dropped. The text is read up to 8,000 characters and the first 120 lines are sent (L1, L2 ...); the page warns about the rest. A text with fewer than 10 Chinese characters is not sent at all.

Document type (DocKit.docTypeOf): the first title found in the first three lines (else anywhere) gives doc_type; none gives unknown and the run names it from the text.

KeyChinese titlesSent as
deposit存款证明, 存款证实书, 存单证明, 资信证明Certificate of Deposit
statement交易明细, 流水, 对账单, 账户明细Bank Statement
income收入证明, 薪资证明, 工资证明Certificate of Income
employment在职证明, 工作证明, 任职证明, 在职收入证明Certificate of Employment
retirement退休证明, 退休证, 离休证Certificate of Retirement
property不动产权证书, 房屋所有权证, 房产证, 房产证明Real Estate Title Certificate
license营业执照Business License
hukou户口簿, 常住人口登记卡, 户籍证明Household Register
birth出生医学证明, 出生证明Birth Certificate
marriage结婚证, 婚姻登记Marriage Certificate
divorce离婚证Divorce Certificate
record无犯罪记录证明, 无犯罪证明Certificate of No Criminal Record
degree学位证书Degree Certificate
diploma毕业证书Graduation Certificate
enrolment在读证明, 在校证明, 学籍证明Certificate of Enrolment
leave准假证明, 请假证明, 同意出国Leave Approval Letter

Dates (DocKit.datesIn): 2024年3月5日 (or 号), 二〇二四年三月五日, 2024.03.05, 2024-3-5, 2024/3/5, an eight-digit 20240305 (only when it is a real date and not part of a longer number) and month-only forms such as 2024年3月 (March 2024). Each is checked against the calendar and written in the date style; one that cannot exist is sent as NOT A REAL DATE and raises bad_date.

Amounts (DocKit.amountsIn): capital numerals such as 伍拾叁万陆仟元整 (with 角 and 分), and figures with a currency mark before them (¥, RMB, CNY, 人民币, USD, $, 美元, HKD, EUR, GBP, JPY ...) or 元, 万元 or 万 after them, so years, IDs and counts are never read as money. 万 multiplies by 10,000 (34.2万元 is CNY 342,000.00). Each is written as a currency code, thousands commas and two decimals; one in capital numerals also gets its English words (Five Hundred Thirty-Six Thousand Yuan Only, ... Yuan Seven Jiao Eight Fen). An amount in capital numerals and a figure of the same currency on the same or the next line must agree.

Numbers (DocKit.numbersIn, DocKit.residentId): an 18-character mainland resident ID has its ISO 7064 MOD 11-2 check character recomputed and its birth date and sex read (the 17th digit is odd for male); the birth date and sex are compared with what the document states. An 11-digit number starting 13 to 19 is a phone number; any other run of 10 to 30 digits is an account or reference number. Digits that belong to a date or an amount are not counted again.

Names (DocKit.namesIn, DocKit.nameToPinyin): a name of two to four characters after a label (姓名, 户名, 存款人, 账户名称, 申请人, 持卡人, 权利人, 户主, 联系人, 法定代表人 and others) or after 兹证明, 兹有 or 证明, spelled SURNAME Given in passport style: surname readings (曾 ZENG, 单 SHAN), two-character surnames (欧阳 OUYANG) and ü written yu after l and n (吕 LYU). A character with more than one reading raises name_check; one with no reading raises name_missing. A spelling typed in spellings wins and is marked (as on the passport).

Terms (DocKit.glossaryIn): the standard English for 82 terms and the major Chinese banks, longest match first.

The 82 terms
ChineseEnglish
中国工商银行Industrial and Commercial Bank of China
中国农业银行Agricultural Bank of China
中国建设银行China Construction Bank
中国银行Bank of China
交通银行Bank of Communications
招商银行China Merchants Bank
中国邮政储蓄银行Postal Savings Bank of China
中信银行China CITIC Bank
中国民生银行China Minsheng Bank
兴业银行Industrial Bank
中国光大银行China Everbright Bank
上海浦东发展银行Shanghai Pudong Development Bank
平安银行Ping An Bank
华夏银行Hua Xia Bank
广发银行China Guangfa Bank
股份有限公司Co., Ltd.
有限责任公司Co., Ltd.
有限公司Co., Ltd.
分行Branch
支行Sub-branch
定期存款time deposit
活期存款demand deposit
整存整取lump-sum deposit and withdrawal
存期deposit term
起息日value date
到期日maturity date
冻结frozen
止付stop payment
余额balance
账号account number
户名account name
币种currency
利率interest rate
存款人depositor
兹证明This is to certify that
特此证明This certificate is hereby issued.
盖章seal
公章official seal
人力资源部Human Resources Department
职务position
职位position
部门department
入职joined the company
月薪monthly salary
年收入annual income
税前before tax
税后after tax
劳动合同employment contract
法定代表人Legal Representative
统一社会信用代码Unified Social Credit Code
注册资本Registered Capital
成立日期Date of Establishment
营业期限Business Term
经营范围Business Scope
住所Domicile
登记机关Registration Authority
退休retired
养老金pension
基本养老保险basic pension insurance
社会保险social insurance
退休金pension
派出所Police Station
公安局Public Security Bureau
户主head of household
籍贯place of origin
民族ethnicity
汉族Han
出生地place of birth
身份证resident ID card
公民身份号码citizen ID number
性别sex
不动产权证书Real Estate Title Certificate
权利人right holder
共有情况co-ownership
坐落location
建筑面积floor area
用途use
住宅residential
本证明仅用于This certificate is only for
签证visa
探亲family visit
旅游tourism

Findings. What the page raises on its own (form.findings). Every warn goes into facts under Page checks: and must come back as a note on its line; info is shown on the page only.

CodeLevelRaised by
bad_datewarna date that is not on the calendar (30 February, month 13)
id_checksumwarnan 18-character resident ID whose check character is wrong - usually one misread digit
name_missingwarna character in a name the pinyin table has no reading for (type the passport spelling)
name_checkinfoa character in a name with more than one reading - match the passport
garbledwarna line with a replacement character (U+FFFD), two or more □ boxes in a row, or three or more question marks
capital_mismatchwarnan amount in capital numerals that disagrees with the figure of the same currency on the same or the next line
id_sexwarna line whose 性别 (sex) disagrees with the sex a valid resident ID encodes
id_birthwarna line with 出生 (birth) whose first date disagrees with the birth date a valid resident ID encodes

DocKit.buildInput writes lines (one line each, L1. and the line as read) and facts (DocKit.factLines): the document type, then one block each - only when it has entries - for the dates, the amounts, the numbers, the names, the terms on one line, and the page checks, each entry with its line id, the original and its English form.

The model's rules

As the system prompt states them, in order:

  1. Translate every line, in order, one entry per line (L1, L2, ...). Never merge, split, drop or reorder lines. A line that is only a heading, a stamp or a signature still gets its entry.
  2. Faithful and formal: translate what is written, in the register of an official certificate. Do not add facts, explanations, titles of address the original does not use, or anything about the applicant's purpose that the text does not say. Do not improve or summarise.
  3. Every date in facts is written exactly in its given English form. Every amount is written exactly in its given form (currency code, thousands commas, two decimals). An amount written in capital numerals comes with its English words in facts; write both, the figure first: CNY 123,456.78 (in words: One Hundred Twenty-Three Thousand Four Hundred Fifty-Six Yuan Seven Jiao Eight Fen). A line that also states the figure does not repeat it.
  4. ID, account, phone and reference numbers are copied digit for digit.
  5. Names use the spelling in facts (SURNAME Given). The first time a person's name appears, follow it with the Chinese in parentheses: WU Zhengye (吴正业). Organisations use the English in facts terms where given; otherwise translate the name and keep the Chinese in parentheses once: Beijing Huaxin Technology Co., Ltd. (北京华信科技有限公司). No other Chinese characters appear in your English.
  6. Seals and signatures: write them in square brackets where they appear: [Seal: Industrial and Commercial Bank of China Beijing Haidian Sub-branch], [Signature]. A line marked (盖章) or (签字) is a seal or signature line.
  7. title is the document's English title as it would head the translation (for example Certificate of Deposit).
  8. notes: one entry per real problem a reader must know about - a line that is garbled or unreadable, a figure that disagrees with its words, a date that cannot exist, a name the page could not read. Copy the page's warnings that apply. No notes about style.
  9. verdict is check when there is at least one note, otherwise ready.
  10. note is one plain sentence (at most 30 words) for the applicant about the translation as a whole.

And the house rules: use only the input. Never invent a number, date, name or place. No backticks and no input field names (task, doc_type, date_style, lines, facts) in any text you write.

The reply

One JSON object, in the shape the system prompt asks for (shown here with line breaks):

{
 "task": "translate",
 "title": "",
 "lines": [
  {
   "line": "L1",
   "en": ""
  }
 ],
 "notes": [
  {
   "line": "L1",
   "note": ""
  }
 ],
 "verdict": "ready | check",
 "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 ready or check reads as unknown), a line id read from its digits (3 or l3 reads as L3), white space squeezed, a line entry with no id and a note with no text dropped.

ReplyKit.trusted(reply, built) maps each line id to its English: an entry for a line that was not sent is left out, a second entry for the same line is ignored (the first is kept), and the lines are shown in the document's order whatever order they came in.

ReplyKit.check(reply, built) lists, under the page's check: a missing verdict, or one that does not follow the notes (check when there is at least one note, else ready); a missing title; a line not translated, translated twice, or not sent; and on every translated line, a date not written in its English form, an amount whose figure is missing, an amount in capital numerals without its English words, an ID, account or phone number not copied digit for digit, a name not spelled as the page (or the passport) spells it, a number the original line does not have (its own digits, its dates and amounts in every form and the values of the Chinese numerals it spells out are allowed), Chinese outside parentheses, and backticks or an input field name. Every warn finding of the page's own that is not carried as a note on its line is listed too. These are warnings and make the status flagged. As notes only: lines out of order, an amount written without its currency code, and a note past 40 words. Otherwise the status is kept. The verdict on the page reads "Ready to print" (ready) or "Check before you print" (check).

What the page builds from it: ReplyKit.translation(built, reply) is the title and every line in the document's order with its original and its English (a missing one marked); the page shows it beside the original and prints the two-page packet - the original (a photo that never leaves the browser, or the text), then the English under the title with the translator's statement: "I certify that the above is a true and accurate translation of the attached document from Chinese into English." with a name, a signature and a date line. ReplyKit.plain, ReplyKit.md and ReplyKit.json write the same as text, a Markdown table with the notes and what the page read, and JSON. Nothing is sent anywhere else: print, copy or download it yourself.

Worked examples - every sample

These are the free samples on the page (every name, ID, account and phone number in them is invented): the text as pasted, what the page reads from it, the exact input it sends, the reply that replays for free, and what the page does with it - the translation beside the original, the notes, the verdict and its check. Together they cover the verdicts ready and check.

A bank deposit certificate

Visa for uk (United Kingdom), so dates are written D Month YYYY. No passport spellings typed.

The text as pasted:

存款证明
编号:BJ2024-03817
兹证明吴正业(身份证号:110108196504120030)在我行存有定期存款,详情如下:
户名:吴正业 账号:6222020200112345678
币种:人民币 存款金额:人民币(大写)伍拾叁万陆仟元整(小写)¥536,000.00
存期:一年 起息日:2024年3月5日 到期日:2025年3月5日
上述存款在本证明有效期内已冻结,不得支取。
本证明有效期至2024年9月30日。
特此证明。
中国工商银行股份有限公司北京海淀支行(盖章)
二〇二四年三月十一日

The page reads 11 lines and recognises Certificate of Deposit (存款证明).

Dates:

LineOriginalEnglish
L62024年3月5日5 March 2024
L62025年3月5日5 March 2025
L82024年9月30日30 September 2024
L11二〇二四年三月十一日11 March 2024

Amounts:

LineOriginalEnglishIn words
L5伍拾叁万陆仟元整CNY 536,000.00Five Hundred Thirty-Six Thousand Yuan Only (agrees with the figure)
L5¥536,000.00CNY 536,000.00-

Numbers:

LineNumberWhat it is
L3110108196504120030resident ID, check character valid; born 1965-04-12, male
L46222020200112345678account or reference number

Names:

LineNameSpelledCheck
L3吴正业WU Zhengye-

Known terms: 兹证明 = This is to certify that; 身份证 = resident ID card; 定期存款 = time deposit; 户名 = account name; 账号 = account number; 币种 = currency; 存期 = deposit term; 起息日 = value date; 到期日 = maturity date; 冻结 = frozen; 特此证明 = This certificate is hereby issued.; 中国工商银行 = Industrial and Commercial Bank of China; 股份有限公司 = Co., Ltd.; 支行 = Sub-branch; 盖章 = seal.

The page raises no findings.

Input:

{
 "task": "translate",
 "doc_type": "Certificate of Deposit",
 "date_style": "D Month YYYY",
 "lines": "L1. 存款证明\nL2. 编号:BJ2024-03817\nL3. 兹证明吴正业(身份证号:110108196504120030)在我行存有定期存款,详情如下:\nL4. 户名:吴正业 账号:6222020200112345678\nL5. 币种:人民币 存款金额:人民币(大写)伍拾叁万陆仟元整(小写)¥536,000.00\nL6. 存期:一年 起息日:2024年3月5日 到期日:2025年3月5日\nL7. 上述存款在本证明有效期内已冻结,不得支取。\nL8. 本证明有效期至2024年9月30日。\nL9. 特此证明。\nL10. 中国工商银行股份有限公司北京海淀支行(盖章)\nL11. 二〇二四年三月十一日",
 "facts": "Document type: 存款证明 -> Certificate of Deposit.\nDates (write each exactly as given):\n- L6 \"2024年3月5日\" -> 5 March 2024\n- L6 \"2025年3月5日\" -> 5 March 2025\n- L8 \"2024年9月30日\" -> 30 September 2024\n- L11 \"二〇二四年三月十一日\" -> 11 March 2024\nAmounts (write each exactly as given):\n- L5 \"伍拾叁万陆仟元整\" -> CNY 536,000.00 (in words: Five Hundred Thirty-Six Thousand Yuan Only)\n- L5 \"¥536,000.00\" -> CNY 536,000.00\nNumbers (copy digit for digit):\n- L3 110108196504120030 resident ID number (checksum valid; born 1965-04-12; male)\n- L4 6222020200112345678 account or reference number\nNames (use these spellings):\n- L3 吴正业 -> WU Zhengye\nTerms: 兹证明 = This is to certify that; 身份证 = resident ID card; 定期存款 = time deposit; 户名 = account name; 账号 = account number; 币种 = currency; 存期 = deposit term; 起息日 = value date; 到期日 = maturity date; 冻结 = frozen; 特此证明 = This certificate is hereby issued.; 中国工商银行 = Industrial and Commercial Bank of China; 股份有限公司 = Co., Ltd.; 支行 = Sub-branch; 盖章 = seal."
}

Reply:

{
 "task": "translate",
 "title": "Certificate of Deposit",
 "lines": [
  {
   "line": "L1",
   "en": "Certificate of Deposit"
  },
  {
   "line": "L2",
   "en": "No.: BJ2024-03817"
  },
  {
   "line": "L3",
   "en": "This is to certify that WU Zhengye (吴正业) (Resident ID Card No.: 110108196504120030) has a time deposit with our bank, the details of which are as follows:"
  },
  {
   "line": "L4",
   "en": "Account Name: WU Zhengye. Account No.: 6222020200112345678."
  },
  {
   "line": "L5",
   "en": "Currency: Renminbi (RMB). Deposit Amount: CNY 536,000.00 (in words: Five Hundred Thirty-Six Thousand Yuan Only)."
  },
  {
   "line": "L6",
   "en": "Deposit Term: one year. Value Date: 5 March 2024. Maturity Date: 5 March 2025."
  },
  {
   "line": "L7",
   "en": "The above deposit has been frozen during the validity period of this certificate and may not be withdrawn."
  },
  {
   "line": "L8",
   "en": "This certificate is valid until 30 September 2024."
  },
  {
   "line": "L9",
   "en": "This certificate is hereby issued."
  },
  {
   "line": "L10",
   "en": "[Seal: Industrial and Commercial Bank of China Co., Ltd. Beijing Haidian Sub-branch]"
  },
  {
   "line": "L11",
   "en": "11 March 2024"
  }
 ],
 "notes": [],
 "verdict": "ready",
 "note": "This is a faithful line-by-line English translation of the certificate of deposit for WU Zhengye; all dates, amounts and ID numbers match the original with no discrepancies."
}

The translation as the page shows and prints it (ReplyKit.translation), under the title Certificate of Deposit:

LineOriginalEnglish
L1存款证明Certificate of Deposit
L2编号:BJ2024-03817No.: BJ2024-03817
L3兹证明吴正业(身份证号:110108196504120030)在我行存有定期存款,详情如下:This is to certify that WU Zhengye (吴正业) (Resident ID Card No.: 110108196504120030) has a time deposit with our bank, the details of which are as follows:
L4户名:吴正业 账号:6222020200112345678Account Name: WU Zhengye. Account No.: 6222020200112345678.
L5币种:人民币 存款金额:人民币(大写)伍拾叁万陆仟元整(小写)¥536,000.00Currency: Renminbi (RMB). Deposit Amount: CNY 536,000.00 (in words: Five Hundred Thirty-Six Thousand Yuan Only).
L6存期:一年 起息日:2024年3月5日 到期日:2025年3月5日Deposit Term: one year. Value Date: 5 March 2024. Maturity Date: 5 March 2025.
L7上述存款在本证明有效期内已冻结,不得支取。The above deposit has been frozen during the validity period of this certificate and may not be withdrawn.
L8本证明有效期至2024年9月30日。This certificate is valid until 30 September 2024.
L9特此证明。This certificate is hereby issued.
L10中国工商银行股份有限公司北京海淀支行(盖章)[Seal: Industrial and Commercial Bank of China Co., Ltd. Beijing Haidian Sub-branch]
L11二〇二四年三月十一日11 March 2024

No notes.

The verdict: ready - "Ready to print"; it follows the notes.

The note: "This is a faithful line-by-line English translation of the certificate of deposit for WU Zhengye; all dates, amounts and ID numbers match the original with no discrepancies."

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

No notes.

An employment certificate for a US trip

Visa for us (United States), so dates are written Month D, YYYY. No passport spellings typed.

The text as pasted:

在职证明
兹证明曾乐女士(身份证号:310104199008230041)自2016年7月1日起在我公司工作至今,现任市场部高级经理,劳动合同期限至2027年6月30日。
其税前月薪为人民币28,500元,年收入约为人民币34.2万元。
我公司同意其于2024年5月18日至2024年6月2日赴美国旅游,假期结束后将按时返回工作岗位,并保留其职位。
特此证明。
公司名称:上海星联信息科技有限公司
地址:上海市徐汇区漕溪北路88号
联系人:人力资源部 王静 电话:13917654321
上海星联信息科技有限公司(盖章)
2024年4月22日

The page reads 10 lines and recognises Certificate of Employment (在职证明).

Dates:

LineOriginalEnglish
L22016年7月1日July 1, 2016
L22027年6月30日June 30, 2027
L42024年5月18日May 18, 2024
L42024年6月2日June 2, 2024
L102024年4月22日April 22, 2024

Amounts:

LineOriginalEnglishIn words
L3人民币28,500元CNY 28,500.00-
L3人民币34.2万元CNY 342,000.00from 万 (10,000)

Numbers:

LineNumberWhat it is
L2310104199008230041resident ID, check character valid; born 1990-08-23, female
L813917654321phone number

Names:

LineNameSpelledCheck
L2曾乐ZENG Le乐 has more than one reading

Known terms: 兹证明 = This is to certify that; 身份证 = resident ID card; 劳动合同 = employment contract; 税前 = before tax; 月薪 = monthly salary; 年收入 = annual income; 旅游 = tourism; 职位 = position; 特此证明 = This certificate is hereby issued.; 有限公司 = Co., Ltd.; 人力资源部 = Human Resources Department; 盖章 = seal.

The page's own findings:

Input:

{
 "task": "translate",
 "doc_type": "Certificate of Employment",
 "date_style": "Month D, YYYY",
 "lines": "L1. 在职证明\nL2. 兹证明曾乐女士(身份证号:310104199008230041)自2016年7月1日起在我公司工作至今,现任市场部高级经理,劳动合同期限至2027年6月30日。\nL3. 其税前月薪为人民币28,500元,年收入约为人民币34.2万元。\nL4. 我公司同意其于2024年5月18日至2024年6月2日赴美国旅游,假期结束后将按时返回工作岗位,并保留其职位。\nL5. 特此证明。\nL6. 公司名称:上海星联信息科技有限公司\nL7. 地址:上海市徐汇区漕溪北路88号\nL8. 联系人:人力资源部 王静 电话:13917654321\nL9. 上海星联信息科技有限公司(盖章)\nL10. 2024年4月22日",
 "facts": "Document type: 在职证明 -> Certificate of Employment.\nDates (write each exactly as given):\n- L2 \"2016年7月1日\" -> July 1, 2016\n- L2 \"2027年6月30日\" -> June 30, 2027\n- L4 \"2024年5月18日\" -> May 18, 2024\n- L4 \"2024年6月2日\" -> June 2, 2024\n- L10 \"2024年4月22日\" -> April 22, 2024\nAmounts (write each exactly as given):\n- L3 \"人民币28,500元\" -> CNY 28,500.00\n- L3 \"人民币34.2万元\" -> CNY 342,000.00\nNumbers (copy digit for digit):\n- L2 310104199008230041 resident ID number (checksum valid; born 1990-08-23; female)\n- L8 13917654321 phone number\nNames (use these spellings):\n- L2 曾乐 -> ZENG Le\nTerms: 兹证明 = This is to certify that; 身份证 = resident ID card; 劳动合同 = employment contract; 税前 = before tax; 月薪 = monthly salary; 年收入 = annual income; 旅游 = tourism; 职位 = position; 特此证明 = This certificate is hereby issued.; 有限公司 = Co., Ltd.; 人力资源部 = Human Resources Department; 盖章 = seal."
}

Reply:

{
 "task": "translate",
 "title": "Certificate of Employment",
 "lines": [
  {
   "line": "L1",
   "en": "Certificate of Employment"
  },
  {
   "line": "L2",
   "en": "This is to certify that Ms. ZENG Le (曾乐) (resident ID card No.: 310104199008230041) has been working in our company since July 1, 2016 up to now, currently holding the position of Senior Manager of the Marketing Department, with the employment contract term valid until June 30, 2027."
  },
  {
   "line": "L3",
   "en": "Her monthly salary before tax is CNY 28,500.00, and her annual income is approximately CNY 342,000.00."
  },
  {
   "line": "L4",
   "en": "Our company agrees that she may travel to the United States for tourism from May 18, 2024 to June 2, 2024; after the vacation ends, she will return to her work post on schedule, and her position will be retained."
  },
  {
   "line": "L5",
   "en": "This certificate is hereby issued."
  },
  {
   "line": "L6",
   "en": "Company Name: Shanghai Xinglian Information Technology Co., Ltd. (上海星联信息科技有限公司)"
  },
  {
   "line": "L7",
   "en": "Address: No. 88 Caoxi North Road, Xuhui District, Shanghai"
  },
  {
   "line": "L8",
   "en": "Contact: Human Resources Department, WANG Jing (王静). Tel.: 13917654321"
  },
  {
   "line": "L9",
   "en": "[Seal: Shanghai Xinglian Information Technology Co., Ltd.]"
  },
  {
   "line": "L10",
   "en": "April 22, 2024"
  }
 ],
 "notes": [],
 "verdict": "ready",
 "note": "This translation matches the certificate of employment exactly as written; no unreadable text, mismatched figures or other issues were found."
}

The translation as the page shows and prints it (ReplyKit.translation), under the title Certificate of Employment:

LineOriginalEnglish
L1在职证明Certificate of Employment
L2兹证明曾乐女士(身份证号:310104199008230041)自2016年7月1日起在我公司工作至今,现任市场部高级经理,劳动合同期限至2027年6月30日。This is to certify that Ms. ZENG Le (曾乐) (resident ID card No.: 310104199008230041) has been working in our company since July 1, 2016 up to now, currently holding the position of Senior Manager of the Marketing Department, with the employment contract term valid until June 30, 2027.
L3其税前月薪为人民币28,500元,年收入约为人民币34.2万元。Her monthly salary before tax is CNY 28,500.00, and her annual income is approximately CNY 342,000.00.
L4我公司同意其于2024年5月18日至2024年6月2日赴美国旅游,假期结束后将按时返回工作岗位,并保留其职位。Our company agrees that she may travel to the United States for tourism from May 18, 2024 to June 2, 2024; after the vacation ends, she will return to her work post on schedule, and her position will be retained.
L5特此证明。This certificate is hereby issued.
L6公司名称:上海星联信息科技有限公司Company Name: Shanghai Xinglian Information Technology Co., Ltd. (上海星联信息科技有限公司)
L7地址:上海市徐汇区漕溪北路88号Address: No. 88 Caoxi North Road, Xuhui District, Shanghai
L8联系人:人力资源部 王静 电话:13917654321Contact: Human Resources Department, WANG Jing (王静). Tel.: 13917654321
L9上海星联信息科技有限公司(盖章)[Seal: Shanghai Xinglian Information Technology Co., Ltd.]
L102024年4月22日April 22, 2024

No notes.

The verdict: ready - "Ready to print"; it follows the notes.

The note: "This translation matches the certificate of employment exactly as written; no unreadable text, mismatched figures or other issues were found."

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

No notes.

A retirement certificate with a smudge

Visa for au (Australia), so dates are written D Month YYYY. No passport spellings typed.

The text as pasted:

退休证明
姓名:欧阳建华 性别:男 出生日期:1955年1月7日
身份证号:440303195501070010
原工作单位:广州市□□机械厂
该同志于2015年1月31日办理退休手续,现每月领取基本养老金人民币6,830.50元。
特此证明。
广州市海珠区社会保险基金管理中心(盖章)
2024年2月26日

The page reads 8 lines and recognises Certificate of Retirement (退休证明).

Dates:

LineOriginalEnglish
L21955年1月7日7 January 1955
L52015年1月31日31 January 2015
L82024年2月26日26 February 2024

Amounts:

LineOriginalEnglishIn words
L5人民币6,830.50元CNY 6,830.50-

Numbers:

LineNumberWhat it is
L3440303195501070010resident ID, fails its check character (it should end in 6)

Names:

LineNameSpelledCheck
L2欧阳建华OUYANG Jianhua-

Known terms: 退休 = retired; 性别 = sex; 身份证 = resident ID card; 养老金 = pension; 特此证明 = This certificate is hereby issued.; 社会保险 = social insurance; 盖章 = seal.

The page's own findings:

Input:

{
 "task": "translate",
 "doc_type": "Certificate of Retirement",
 "date_style": "D Month YYYY",
 "lines": "L1. 退休证明\nL2. 姓名:欧阳建华 性别:男 出生日期:1955年1月7日\nL3. 身份证号:440303195501070010\nL4. 原工作单位:广州市□□机械厂\nL5. 该同志于2015年1月31日办理退休手续,现每月领取基本养老金人民币6,830.50元。\nL6. 特此证明。\nL7. 广州市海珠区社会保险基金管理中心(盖章)\nL8. 2024年2月26日",
 "facts": "Document type: 退休证明 -> Certificate of Retirement.\nDates (write each exactly as given):\n- L2 \"1955年1月7日\" -> 7 January 1955\n- L5 \"2015年1月31日\" -> 31 January 2015\n- L8 \"2024年2月26日\" -> 26 February 2024\nAmounts (write each exactly as given):\n- L5 \"人民币6,830.50元\" -> CNY 6,830.50\nNumbers (copy digit for digit):\n- L3 440303195501070010 resident ID number (FAILS its checksum)\nNames (use these spellings):\n- L2 欧阳建华 -> OUYANG Jianhua\nTerms: 退休 = retired; 性别 = sex; 身份证 = resident ID card; 养老金 = pension; 特此证明 = This certificate is hereby issued.; 社会保险 = social insurance; 盖章 = seal.\nPage checks:\n- L3: ID number 440303195501070010 fails its checksum (the last character should be 6) - a digit was probably misread.\n- L4 has unreadable characters - type that part from the original."
}

Reply:

{
 "task": "translate",
 "title": "Certificate of Retirement",
 "lines": [
  {
   "line": "L1",
   "en": "Certificate of Retirement"
  },
  {
   "line": "L2",
   "en": "Name: OUYANG Jianhua (欧阳建华) Sex: Male Date of Birth: 7 January 1955"
  },
  {
   "line": "L3",
   "en": "Resident ID Card No.: 440303195501070010"
  },
  {
   "line": "L4",
   "en": "Former Work Unit: Guangzhou □□ Machinery Factory"
  },
  {
   "line": "L5",
   "en": "The said comrade completed retirement formalities on 31 January 2015 and currently receives a basic pension of CNY 6,830.50 per month."
  },
  {
   "line": "L6",
   "en": "This certificate is hereby issued."
  },
  {
   "line": "L7",
   "en": "[Seal: Guangzhou Haizhu District Social Insurance Fund Management Center (广州市海珠区社会保险基金管理中心)]"
  },
  {
   "line": "L8",
   "en": "26 February 2024"
  }
 ],
 "notes": [
  {
   "line": "L3",
   "note": "The resident ID card number fails its checksum (the last character should be 6); a digit may have been misread."
  },
  {
   "line": "L4",
   "note": "Part of the former work unit's name is unreadable in the original and could not be translated."
  }
 ],
 "verdict": "check",
 "note": "The ID number fails its checksum and part of the work unit name is unreadable; please verify both against the original."
}

The translation as the page shows and prints it (ReplyKit.translation), under the title Certificate of Retirement:

LineOriginalEnglish
L1退休证明Certificate of Retirement
L2姓名:欧阳建华 性别:男 出生日期:1955年1月7日Name: OUYANG Jianhua (欧阳建华) Sex: Male Date of Birth: 7 January 1955
L3身份证号:440303195501070010Resident ID Card No.: 440303195501070010
L4原工作单位:广州市□□机械厂Former Work Unit: Guangzhou □□ Machinery Factory
L5该同志于2015年1月31日办理退休手续,现每月领取基本养老金人民币6,830.50元。The said comrade completed retirement formalities on 31 January 2015 and currently receives a basic pension of CNY 6,830.50 per month.
L6特此证明。This certificate is hereby issued.
L7广州市海珠区社会保险基金管理中心(盖章)[Seal: Guangzhou Haizhu District Social Insurance Fund Management Center (广州市海珠区社会保险基金管理中心)]
L82024年2月26日26 February 2024

Notes:

The verdict: check - "Check before you print"; it follows the notes.

The note: "The ID number fails its checksum and part of the work unit name is unreadable; please verify both against the original."

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

No notes.

Build the input yourself

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

const K = DocKit, R = ReplyKit;
const form = K.form({ text: "收入证明\n兹证明李明先生自2019年3月1日起在我公司工作,税前月薪为人民币15,000元。\n特此证明。\n2024年6月3日",
  dest: "ca", spellings: "李明 = LEE Ming", translator: "" });
// form.lines: [{id, text, dates, amounts, numbers}] - L1, L2 ... one per non-empty line
// form.docType: {key, zh, en} or null; form.style: dmy or mdy (from dest)
// form.dates, form.amounts, form.numbers, form.names, form.glossary: what the page read, each with its line
// form.findings: [{code, level, line, text}] - level warn goes into facts, info stays on the page
const built = K.buildInput(form);     // built.ok is false, with built.note, when there is too little Chinese text
// built.warn: what the page warns about (a clipped text, lines past 120)
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)          // {byLine, missing, extra, dup, order}
// R.translation(built, reply)      // {title, lines: [{id, zh, en, missing}]}
// R.plain(built, reply)            // the English as text, with the translator's statement

That gives this wire input - an income certificate for a Canadian visa, two dates, one amount, and a name whose passport spelling overrides the page's pinyin (LI Ming):

{
 "task": "translate",
 "doc_type": "Certificate of Income",
 "date_style": "D Month YYYY",
 "lines": "L1. 收入证明\nL2. 兹证明李明先生自2019年3月1日起在我公司工作,税前月薪为人民币15,000元。\nL3. 特此证明。\nL4. 2024年6月3日",
 "facts": "Document type: 收入证明 -> Certificate of Income.\nDates (write each exactly as given):\n- L2 \"2019年3月1日\" -> 1 March 2019\n- L4 \"2024年6月3日\" -> 3 June 2024\nAmounts (write each exactly as given):\n- L2 \"人民币15,000元\" -> CNY 15,000.00\nNames (use these spellings):\n- L2 李明 -> LEE Ming (as on the passport)\nTerms: 兹证明 = This is to certify that; 税前 = before tax; 月薪 = monthly salary; 特此证明 = This certificate is hereby issued.."
}

Paste keys: text (the document's Chinese text, line by line), dest (uk, us, ca, au, schengen or other; only us gives Month D, YYYY), spellings (lines of Chinese name = Passport Spelling, or empty) and translator (a name for the statement, or empty; never sent). 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://visa-copy.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":"visa-copy"}' | 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": "translate", "doc_type": "Certificate of Deposit", "date_style": "D Month YYYY", "lines": "L1. 存款证明\nL2. 编号:BJ2024-03817\nL3. 兹证明吴正业(身份证号:110108196504120030)在我行存有定期存款,详情如下:\nL4. 户名:吴正业 账号:6222020200112345678\nL5. 币种:人民币 存款金额:人民币(大写)伍拾叁万陆仟元整(小写)¥536,000.00\nL6. 存期:一年 起息日:2024年3月5日 到期日:2025年3月5日\nL7. 上述存款在本证明有效期内已冻结,不得支取。\nL8. 本证明有效期至2024年9月30日。\nL9. 特此证明。\nL10. 中国工商银行股份有限公司北京海淀支行(盖章)\nL11. 二〇二四年三月十一日", "facts": "Document type: 存款证明 -> Certificate of Deposit.\nDates (write each exactly as given):\n- L6 \"2024年3月5日\" -> 5 March 2024\n- L6 \"2025年3月5日\" -> 5 March 2025\n- L8 \"2024年9月30日\" -> 30 September 2024\n- L11 \"二〇二四年三月十一日\" -> 11 March 2024\nAmounts (write each exactly as given):\n- L5 \"伍拾叁万陆仟元整\" -> CNY 536,000.00 (in words: Five Hundred Thirty-Six Thousand Yuan Only)\n- L5 \"¥536,000.00\" -> CNY 536,000.00\nNumbers (copy digit for digit):\n- L3 110108196504120030 resident ID number (checksum valid; born 1965-04-12; male)\n- L4 6222020200112345678 account or reference number\nNames (use these spellings):\n- L3 吴正业 -> WU Zhengye\nTerms: 兹证明 = This is to certify that; 身份证 = resident ID card; 定期存款 = time deposit; 户名 = account name; 账号 = account number; 币种 = currency; 存期 = deposit term; 起息日 = value date; 到期日 = maturity date; 冻结 = frozen; 特此证明 = This certificate is hereby issued.; 中国工商银行 = Industrial and Commercial Bank of China; 股份有限公司 = Co., Ltd.; 支行 = Sub-branch; 盖章 = seal."}
# - 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 visa-copy:<task>:<inputHash>:a<attempt> (the one task is translate), 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="visa-copy:translate:$(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 '.title, .verdict, .note' reply.json
jq -r '.lines[]? | "\(.line): \(.en)"' reply.json   # one per line of the original, in order
jq -r '.notes[]? | "note \(.line): \(.note)"' reply.json   # empty when the verdict is ready

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: visa-copy:translate:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json