가이드
Zap! your ratsnest. 부품 배치를 마친 KiCad 보드를 올리면, 같은 보드에 트랙·비아·카퍼 푸어를 더해 돌려드립니다.
- Base URL:
https://zap.mutual.solutions - 상태: 얼리 액세스. 라우팅된 보드는 제작 전에 반드시 엔지니어가 검토해야 합니다.
- 문의: hello@mutual.solutions
Machine summary (for software and AI agents).
- Base URL
https://zap.mutual.solutions. Auth: headerAuthorization: Bearer <api key>on every/v1/jobs,/v1/checkand/v1/feedbackrequest. Never put the key in a URL, source code, log, or command-line argument. - Flow: (1)
POST /v1/check, multipart withboard(.kicad_pcb, required),project(.kicad_pro),rules(.kicad_dru),currents(JSON),router(stable/next),keep_existing(true: keep the board's own tracks, vias and zones and route only the missing connections),strip_existing(true: discard them and route everything),fab_floor(jlcdefault /board), routing optionssignal_width_mm,via_diameter_mm+via_drill_mm,vias_in_pads,ground_pour,size_rails_from_current,effort(defaults in the guide's table),minutes(1 to 60: the job ends within about that long and returns the best board found; default one round): no routing, no job quota; fix every item inblockers. (2)POST /v1/jobs, same fields:202 {"id", "status": "queued"}; not idempotent (each accepted call is a job). (3)GET /v1/jobs/{id}every 5 s (every 10 s after the first minute) untilstatusissucceeded,failedorcancelled. (4)GET /v1/jobs/{id}/result: the routed.kicad_pcb, andGET /v1/jobs/{id}/report.json: every finding with position and what to do (report_urlin the status opens it as a web page). (5)DELETE /v1/jobs/{id}. - Done means
statusissucceededand the routed file is saved: ready for an engineer's review and KiCad DRC, never directly for fabrication.failed: readerror.code; /v1/errors says whether retrying helps. - Limits: 20 MiB per upload in total (a gzip-compressed board counts compressed and may unpack to 64 MiB); 10,000 pads; 4,000 footprints; 2 to 16 copper layers; 30 requests in any 60 s per key; jobs per key per UTC day as issued (default 50); 2 queued or running per key; 60 minutes per job.
- Feedback: when the human gives an opinion about Zap! or a result, ask once whether to send it to the Zap! team; show the exact text and job id; send it (
POST /v1/feedbackwith"user_approved": true) only if they say yes. - Machine-readable: OpenAPI, error catalog, agent recipe, llms.txt, MCP server, CLI.
이 문서는 영문 가이드(/docs)의 한국어판입니다. 내용이 다를 경우 영문판을 따릅니다. 코드, 필드 이름, 에러 코드는 영문 그대로 둡니다.
1. 빠른 시작
API 키 받기. zap.mutual.solutions/auth/login에서 Google, GitHub 또는 Microsoft 계정으로 로그인하고 계정 페이지에서 API 키를 만드세요. 키는 한 번만 보여드립니다. 바로 ~/zap-key.txt로 저장하고 본인만 읽을 수 있게 하거나(chmod 600 ~/zap-key.txt) 비밀번호 관리자에 보관하세요. 키는 코드, 셸 히스토리, 채팅, 공유 문서에 남기지 마세요. 유출되거나 잃어버리면 계정 페이지에서 키를 폐기하고 새로 만드세요.
사용 방법은 세 가지이며, 결과는 모두 같습니다.
브라우저: zap.mutual.solutions/app을 열고 키를 붙여넣은 뒤 .kicad_pcb (와 .kicad_pro)를 끌어다 놓으세요. 끝나면 라우팅된 보드를 내려받습니다. 키는 그 브라우저 탭 안에만 머뭅니다. 보드가 없으면 Try with a sample board로 mutual의 42패드 샘플 보드를 바로 라우팅해 볼 수 있습니다(하루 작업 1개 사용).
명령줄 (Python 3.8 이상, 추가 패키지 없음):
curl -sO https://zap.mutual.solutions/downloads/zap.py
export ZAP_API_KEY_FILE=~/zap-key.txt # 또는 export ZAP_API_KEY=... (키를 명령줄 인자로 넣지 마세요)
python3 zap.py demo # 보드가 없으면: mutual의 42패드 샘플 보드를 받아 라우팅(하루 작업 1개 사용)
python3 zap.py check myboard.kicad_pcb # 3초쯤(최대 30초쯤), 라우팅 없음, 작업 할당량 사용 안 함: 작업을 막거나 약하게 할 문제 확인
python3 zap.py run myboard.kicad_pcb # 먼저 점검한 뒤 myboard.kicad_pcb(+ 같은 이름의 .kicad_pro / .kicad_dru)를
# 올리고, 기다린 뒤 myboard.routed.kicad_pcb와 전체 보고서
# myboard.routed.zap-report.json을 저장하고 서버 사본을 지움(--keep: 7일 보관)
python3 zap.py --help # run, check, submit, status, wait, download, delete, list, version
HTTP API (curl):
URL=https://zap.mutual.solutions
# 키는 bash 내장 printf가 만든 헤더 파일로 curl에 전달됩니다: 명령줄에 절대 나타나지 않습니다
zcurl() { curl -s -H @<(printf 'Authorization: Bearer %s\n' "$(cat zap-key.txt)") "$@"; }
# 1. 점검한 뒤 배치가 끝난 보드를 제출하고 작업 id를 저장 (.kicad_pro가 없으면 "-F project=..."를 빼세요)
zcurl -F board=@myboard.kicad_pcb -F project=@myboard.kicad_pro $URL/v1/check # "ok": true면 준비 완료
ID=$(zcurl -F board=@myboard.kicad_pcb -F project=@myboard.kicad_pro \
$URL/v1/jobs | python3 -c 'import json,sys; r=json.load(sys.stdin); print(r.get("id") or r)')
echo "$ID" # 32자리 작업 id. 다른 내용이 나오면 그것이 에러 메시지입니다
# 20 MiB가 넘는 보드: gzip -k myboard.kicad_pcb 후 -F board=@myboard.kicad_pcb.gz로 보내세요(풀었을 때 64 MiB까지)
# 2. 작업이 끝날 때까지 대기(5초 간격)
while :; do
ST=$(zcurl $URL/v1/jobs/$ID | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("status") or d.get("error"))')
case "$ST" in queued|running) sleep 5;; *) break;; esac
done
zcurl $URL/v1/jobs/$ID > myboard.status.json
# {"id":"...","status":"succeeded", ..., "metrics":{"unconnected":0,"critical_unconnected":0,"drc_errors":0,"wall_time_s":5.1,"fab":{...}}}
# 3. 라우팅된 보드를 다운로드하고 서버 사본 삭제
zcurl -f -o myboard.routed.kicad_pcb $URL/v1/jobs/$ID/result && cp myboard.kicad_pro myboard.routed.kicad_pro
zcurl -X DELETE $URL/v1/jobs/$ID
Python (pip install requests 필요):
import os, time, requests
URL = "https://zap.mutual.solutions"
H = {"Authorization": f"Bearer {open('zap-key.txt').read().strip()}"}
files = {"board": open("myboard.kicad_pcb", "rb")}
if os.path.exists("myboard.kicad_pro"): # 선택: 디자인 룰
files["project"] = open("myboard.kicad_pro", "rb")
job = requests.post(f"{URL}/v1/jobs", headers=H, files=files).json()
if "id" not in job:
raise SystemExit(job["message"])
while True:
st = requests.get(f"{URL}/v1/jobs/{job['id']}", headers=H).json()
if st["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(5)
if st["status"] == "succeeded":
r = requests.get(f"{URL}/v1/jobs/{job['id']}/result", headers=H)
open("myboard.routed.kicad_pcb", "wb").write(r.content)
print(st["metrics"])
else:
print(st["error"])
2. 업로드할 파일
| 필드 | 파일 | 필수 여부 | 설명 |
|---|---|---|---|
board |
.kicad_pcb |
필수 | KiCad 6, 7, 8, 9 또는 10에서 저장한 보드(KiCad 5 보드는 새 KiCad에서 열어 한 번 저장하세요). 모든 풋프린트가 배치되고 보드 외곽선이 Edge.Cuts에 그려져 있어야 하며, strip_existing=true(기존 트랙과 비아를 지우고 전부 라우팅)나 keep_existing=true(그대로 두고 빠진 연결만 라우팅)를 보내는 경우가 아니면 트랙이나 비아가 아직 없어야 합니다. gzip으로 압축해 보내도 됩니다(같은 필드에 board.kicad_pcb.gz, 풀었을 때 64 MiB까지): 큰 보드도 업로드 한도에 들어가며, zap.py, MCP 도구와 웹 페이지는 모든 보드를 알아서 압축합니다. |
project |
.kicad_pro |
권장 | 디자인 룰과 넷 클래스(트랙 폭, 클리어런스, 비아 크기). 없으면 KiCad 기본 룰(클리어런스·트랙 폭 0.2 mm, 보드 가장자리에서 0.5 mm)로 라우팅하고 검사합니다. 보드와 같은 KiCad 프로젝트의 파일이어야 합니다. |
rules |
.kicad_dru |
선택 | 커스텀 디자인 룰을 쓰는 경우. |
currents |
.json 파일(또는 같은 JSON을 텍스트 필드로) |
선택 | 알고 있는 넷 전류. 아래 "알고 있는 전류"를 보세요. |
fab_floor |
jlc / board |
선택 | jlc(기본): JLC 공정 능력 아래로는 절대 내려가지 않습니다. 더 가는 카퍼가 필요한 넷은 라우팅하지 않고 이유와 함께 알려드립니다. board: 보드 자체의 최소값을 따릅니다(JLC보다 정밀한 제조사용). JLC 관련 지적은 그대로 보고합니다. 작업마다 기록되며, 넷 클래스가 JLC보다 가늘고 fab_floor가 jlc이면 점검이 경고(rules_finer_than_jlc)합니다. |
router |
stable / next |
선택 | 기본값 stable. next는 새 라우터 빌드로, GET /v1/version의 routers.next에 빌드가 있을 때만 쓸 수 있습니다. 없으면 router_unavailable로 거부됩니다. 한도와 할당량은 같습니다. |
keep_existing |
true / false |
선택 | 기본값 false. 일부를 직접 라우팅한 보드라면 true: 기존 트랙, 비아, 존을 그대로 두고 빠진 연결만 그 사이로 라우팅합니다. 다른 값은 거부됩니다(invalid_keep_existing). |
strip_existing |
true / false |
선택 | 기본값 false. 예전 라우팅이 남은 보드라면 true: 트랙과 비아를 지우고 전부 라우팅합니다(KiCad의 Edit > Global Deletions와 같음). keep_existing과 함께 보낼 수 없습니다. |
consent_training |
true / false |
선택 | 기본값 false. 다른 값은 거부됩니다(invalid_consent). 7절 참고. |
알고 있는 전류. 일부 넷에 흐르는 전류를 알고 있다면(모터 전원, 배터리 입력, 레귤레이터 출력 등) 함께 보내주세요. Zap!은 그 넷의 카퍼를 자체 추정 대신 보내주신 값에 맞춰 만들고, 발열 점검에도 그 값을 씁니다:
{"nets": {"+5V": {"amps": 3.0}, "VBAT": {"amps": 2.5, "peak_amps": 6.0}}, "ambient_c": 40}
넷 이름은 보드에 있는 이름(PCB 에디터의 Net Inspector에서 확인)을 그대로 쓰거나, 보드에서 하나뿐이라면 마지막 부분만 써도 됩니다(/PoE_5V 대신 PoE_5V). 그라운드가 아니라 전원 넷(레귤레이터나 커넥터가 공급하는 레일)을 적어주세요. 그라운드 전류를 적으면 그 넷의 모든 부품에 고르게 나눠 계산하므로 의미 있는 결과가 나오지 않습니다. amps는 연속(RMS) 전류로 0~100 A이며 10 °C 점검에 쓰입니다. peak_amps(선택, amps 이상)는 기록만 합니다. ambient_c(선택, -40~125)를 보내면 가장 뜨거운 카퍼의 절대 온도가 함께 나옵니다. 넷은 최대 2,000개입니다. 보내지 않은 넷은 보드 부품으로 추정한 값을 그대로 씁니다. 브라우저에서는 보드와 함께 .json을 끌어다 놓고, 명령줄에서는 python3 zap.py run myboard.kicad_pcb --currents currents.json처럼 씁니다.
라우팅 옵션과 기본값. 각 설정은 옵션을 보내면 그 값, 아니면 보드 룰(.kicad_pro, .kicad_dru), 그것도 없으면 아래 기본값을 씁니다. .kicad_pro가 없으면 보드 룰은 KiCad 기본값이고, 보고서에 "No project file: defaults used"가 표시됩니다. 모든 보고서에 실제로 쓴 설정과 출처가 나옵니다. 기본값은 GET /v1/version의 라우터 빌드에 속하며, 기본값이 바뀌면 릴리스 노트에 적습니다.
| 설정 | 업로드 필드, 값 | 아무것도 보내지 않을 때의 기본값 |
|---|---|---|
| 신호 트랙 폭 | signal_width_mm, 0.05~5 mm |
보드 Default 넷 클래스 폭, 없으면 0.2 mm |
| 비아 지름과 드릴 | via_diameter_mm + via_drill_mm, mm, 드릴은 0.1 mm 이상 작게 |
보드 Default 넷 클래스 비아, 없으면 0.6 / 0.3 mm. 다른 넷 클래스는 자기 값 유지 |
| 클리어런스, 다른 넷 클래스 | (보드에서) | 보드 넷 클래스, 없으면 0.2 mm |
| 최소 크기 | fab_floor: jlc, board |
jlc: JLC 이하로 내려가지 않음(2층 트랙 0.127 mm, 비아 0.4 / 0.3 mm; 4층 이상 0.09 mm, 0.25 / 0.15 mm). 그보다 작은 옵션은 거부 |
| 전원 트랙 폭 | size_rails_from_current: on, off |
on: 공간이 되는 곳에서 전류에 맞춰(전류 파일, 없으면 부품으로 추정) 넓히며 넷 클래스 폭보다 좁아지지 않음. 넷 클래스 폭에 머무는 레일이 있을 수 있음(840패드 시험 보드, 프로젝트 파일 없음: 2.94 A 레일이 0.2 mm). off: 넷 클래스 폭, 과열될 수 있음. 보고서는 10 °C를 넘는 구간을 모두 세고 가장 뜨거운 10곳을 위치와 함께 보여줌 |
| 비아 비용 | (아직 조정 불가) | normal |
| SMD 패드 안 비아 | vias_in_pads: forbid, allow |
forbid. allow는 비아를 메우고 덮는(filled and capped) 추가 제작 옵션으로 만들어야 함 |
| 그라운드 푸어 | ground_pour: on, off |
on, 모든 카퍼 레이어 |
| 그라운드 스티칭 비아 | (아직 조정 불가) | on |
| 카퍼 레이어 | (보드에서) | 모든 카퍼 레이어. 4층 이상은 In1(6층 이상은 끝에서 두 번째 내층도)을 플레인으로 둠 |
| 라우팅 노력 | effort: quick, normal, thorough |
normal. thorough: 라우팅 작업량이 약 2배이고 중간 결과 중 가장 좋은 것을 남깁니다. 큰 보드에서는 느리지만 보통 빠진 연결이 줄어듭니다. quick: 작업량 약 8분의 1, 빠르지만 빠진 연결이 늘어납니다 |
| 시간 예산 | minutes, 1~60 |
한 라운드(시도 5개), 보통 몇 분(아래 "시간 예산" 참고) |
| 기존 카퍼 | keep_existing 또는 strip_existing: true, false |
false: 트랙이나 비아가 있는 보드는 거부(input_has_copper) |
| 라우터 | router: stable, next |
stable |
범위를 벗어난 값은 invalid_option으로 거부됩니다(option은 필드, fix는 보낼 값).
시간 예산(minutes). minutes: 최대 N분(1~60) 동안 Zap!이 계속 시도해 가장 좋은 결과를 돌려줍니다. 기본값은 몇 분 걸리는 한 라운드입니다. 점검과 보고서까지 포함한 작업 전체가 약 N분 안에 끝납니다. 첫 라운드만으로 더 걸릴 보드는 각 시도가 제때 탐색을 멈추고 그때까지 만든 보드를 마무리하며, 잇지 못한 연결은 미연결로 나옵니다. 탐색을 멈춘 뒤 마무리에도 시간이 걸리며 몇 분이 드는 보드도 있습니다. N분 안에 끝난 시도가 없으면 처음 끝난 시도를 요청보다 늦게 돌려주고, metrics.time_budget.stopped가 over_time이 됩니다. 첫 라운드(설정 5개 동시) 뒤에도 시간이 남으면 같은 보드를 한 번에 최대 6개씩 다시 라우팅합니다. 같은 설정 5개를 작업량 2배로, 남은 시간이 충분하면 4배로, 그다음 effort=thorough와 다른 설정으로 시도합니다. 결과는 평소와 같은 순위 (빠진 연결이 적은 것, 그다음 쇼트와 룰 에러, 그다음 비아)로 가장 좋은 것을 돌려줍니다. 새 시도는 이 보드의 첫 라운드 시간으로 보아 제시간에 끝날 수 있을 때만 시작하고, 라우팅은 늦어도 50분에 끝나므로 점검과 보고서가 작업 한도 60분 안에 들어갑니다. 빠진 연결도 쇼트나 클리어런스 에러도 없는 시도가 나오면 일찍 끝냅니다. minutes가 10을 넘는 작업은 서비스 전체에서 한 번에 하나만 실행됩니다. 다른 그런 작업은 대기열에서 기다리며(상태의 queue_note가 알려줌), minutes가 없거나 10 이하인 작업은 막히지 않습니다. 결과의 metrics.time_budget에 시도 횟수, 고른 시도, 걸린 시간이 나옵니다(3절). 명령줄: --minutes 20. 키 없는 무료 체험은 한 라운드만 라우팅합니다.
업로드 전에 확인하세요.
- 풋프린트는 배치한 위치 그대로 둡니다. Zap!은 부품을 옮기지 않습니다.
- 기존 카퍼 푸어는 Zap!이 만든 그라운드·전원 푸어로 대체되며, 푸어의 넷은 Zap!이 정합니다(
keep_existing=true이면 그대로 둡니다). 사전 점검은 대체될 푸어를 넷과 레이어로 알려 주고(existing_pours_replaced), 보고서는 대체된 푸어와 Zap!이 만든 푸어를 보여 줍니다(pours). - 예전 트랙과 비아. 트랙이나 비아가 남은 보드는 거부됩니다(
input_has_copper).strip_existing=true(브라우저: "지우고 전부 라우팅" 체크, 명령줄:--strip-existing)로 처음부터 라우팅하거나keep_existing=true로 유지하세요. - 일부만 라우팅한 보드. 중요한 넷(예: 고속 차동쌍, 전원 경로)을 직접 라우팅한 뒤
keep_existing=true로 보내세요 (브라우저: "My board is partly routed" 체크, 명령줄:--keep-existing). 기존 트랙, 비아, 존은 바뀌지 않고, Zap!은 빠진 연결만 더합니다. 몇 개를 유지하고 몇 개를 더했는지는 작업 상태의metrics.existing_copper에 나옵니다. - 넷 클래스와 룰은 KiCad에서 먼저 확정하세요. Zap!은 보내주신 룰대로 라우팅합니다.
라우팅 전 점검. POST /v1/check는 POST /v1/jobs와 같은 필드를 받고, 라우팅 없이 대부분의 보드는 3초쯤(가장 큰 10,000패드 보드는 30초쯤) 안에 답하며, 하루 작업 수에 들어가지 않습니다(요청 횟수 한도에는 들어가며, 키당 한 번에 하나씩). 브라우저 페이지는 키와 보드가 있으면 바로 점검하고, zap.py run은 제출 전에 점검합니다. 답은 다음과 같습니다:
{"ok": false, "summary": "1 problem would stop the job: fix it first. 1 warning.",
"board": {"kicad_version": 8, "file_version": 20240108, "returned_as": "a KiCad 8 file, like the one you sent",
"footprints": 19, "pads": 94, "nets": 42, "copper_layers": 2, "size_mm": [88.64, 78.81],
"rules": {"project": "uploaded", "custom_rules": "none"}, "router": {"choice": "stable", "build": "..."}},
"blockers": [{"code": "footprints_outside_board", "message": "...", "footprints": [{"ref": "R4", "at_mm": [400, 400], "side": "front", "whole_footprint": true}]}],
"warnings": [{"code": "courtyards_overlap", "message": "...", "items": [{"refs": ["R4", "RV1"], "at_mm": [118.69, 121.82]}]}]}
blockers는 작업을 막는 문제입니다: 5절의 거부 코드 전부(예: input_has_copper, detail이 no_outline인 invalid_board, invalid_currents, 외곽선 밖 풋프린트의 참조 번호와 위치가 붙는 footprints_outside_board, 라우팅 시도 한 번의 예측 메모리와 외곽, 카퍼 레이어, 그리드 셀 수가 memory에 붙는 board_too_large)와 unsupported_rules(라우터가 적용하지 못하는 커스텀 룰과 함수). warnings는 결과를 약하게 하는 문제입니다: no_project(KiCad 기본 룰 사용), unknown_net_class(지정했지만 정의하지 않은 넷 클래스), rules_finer_than_jlc(넷 클래스가 JLC 최소값보다 가늘어 일부 넷이 라우팅되지 않을 수 있음: 제조사가 지원하면 fab_floor=board), courtyards_overlap와 malformed_courtyard(보내주신 룰로 돌린 KiCad 자체 점검의 참조 번호와 위치), check_incomplete(점검 일부를 실행하지 못함), very_large_board(외곽선이 매우 커서 메모리 한도에 걸릴 수 있음: 실패하면 KiCad에서 패널을 보드별로 나눠 하나씩 라우팅). 메시지는 무엇을 KiCad의 어디에서 고치면 되는지 알려줍니다(메시지는 영어입니다). 위치는 KiCad에 표시되는 mm 좌표입니다.
3. 돌려받는 것
routed.kicad_pcb: 트랙, 비아, 카퍼 푸어가 추가된 보드. 올리신 것과 같은 KiCad 파일 버전으로 돌려드리므로 쓰시던 KiCad에서 그대로 열립니다(KiCad 6, 7, 8, 9, 10 보드로 확인). 카퍼 푸어는 KiCad 자체 존 필러가 보내주신 룰로 채우므로 킵아웃 영역과 보드 가장자리를 피합니다.- 라우팅된 카퍼가 서로 다른 넷을 쇼트시킨 보드는 돌려드리지 않습니다. 그런 작업은
output_has_shorts로 실패합니다. - Zap!은 JLC 최소 크기 아래로는 라우팅하지 않고(1-2층: 트랙과 간격 0.127 mm, 비아 드릴 0.3 mm, 비아 0.4 mm; 4층 이상: 0.09 mm, 0.15 mm, 0.25 mm), SMD 패드 안에 비아를 두지 않습니다. 넷 클래스가 그보다 가는 카퍼를 요구하면 그 넷은 라우팅되지 않을 수 있으며, 보고서에 그 이유와 함께 나옵니다.
- 요약 수치. 카퍼 푸어를 채운 뒤 돌려드리는 파일에 KiCad 자체 DRC를 돌려 측정합니다. 모두 개수이며 낮을수록 좋고, 0이 가장 좋습니다.
| 지표 | 의미 |
|---|---|
unconnected |
아직 이어지지 않은 연결 수 |
critical_unconnected |
그중 전원, 그라운드, 클럭, 리셋, 부트, 디버그 넷의 연결 수(unconnected보다 클 수 없음) |
drc_errors |
업로드한 룰 기준의 디자인 룰 에러. 배치 자체에서 생긴 문제(예: 코트야드 겹침)도 포함합니다 |
wall_time_s |
서버에서 작업에 걸린 시간(초) |
time_budget |
minutes를 보낸 작업만: attempts(시작한 시도 수, 첫 라운드 5개 포함), attempts_finished, rounds, routing_s(작업 시작부터 라우팅이 끝날 때까지 초), winner_attempt와 winner(standard, work_x2, work_x4, alternative), winner_setting, stopped(complete, time, settings_used_up, memory), 그리고 같은 내용을 한 문장으로 쓴 summary. 문제 개수가 아니라 집계입니다 |
unconnected가 0이 아니면 남은 연결은 KiCad에서 직접 마무리하세요. 보드를 주문하기 전에는 항상 직접 DRC를 돌리고 검토하세요.
기존 카퍼(metrics.existing_copper, keep_existing=true 작업만). 문제 개수가 아니라 개수 집계입니다: tracks_kept, vias_kept, zones_kept는 올리신 개수와 같아야 하고 tracks_removed, vias_removed, zones_removed는 0이어야 합니다(Zap!은 기존 카퍼를 지우지 않습니다. 0이 아니면 결함이니 알려주세요). tracks_added, vias_added는 라우터가 더한 개수입니다.
제작 점검(metrics.fab). 라우팅된 모든 보드는 위 수치와 같은 룰로 과열과 제작 가능성도 점검합니다. 점검은 결과를 알려줄 뿐이며, Zap!이 어떤 라우팅 결과를 돌려줄지에는 영향을 주지 않습니다.
| 필드 | 의미 |
|---|---|
check |
점검 버전(예: fab1 1.6) |
rules |
사용한 룰: {"project": "uploaded" 또는 "kicad_defaults", "custom_rules": "uploaded" 또는 "none"} |
computed |
점검이 실행되지 않았으면 false와 reason(time limit, check failed). 라우팅된 보드는 그래도 돌려드립니다 |
thermal.worst_rise_c |
가장 뜨거운 카퍼 구간의 온도 상승(주변 온도 대비 °C, IPC-2152 기준; 낮을수록 좋음; 한계는 thermal.limit_c, 10 °C; 1000은 도표 범위를 크게 넘었다는 뜻) |
thermal.sections_over_limit |
한계를 넘는 트랙·비아·카퍼 푸어 구간 수(낮을수록 좋음, 0이 최선) |
thermal.nets_over_limit |
그 구간들이 속한 넷(라우팅된 보드와 함께 보관하며, 보드가 삭제되면 null) |
thermal.hottest |
가장 뜨거운 구간 최대 10개: net, kind(track, via, pour), layer, rise_c, current_a, current_source(stated: 보내주신 값, estimated: 보드 부품으로 추정한 값), at_mm(x, y). 라우팅된 보드와 함께 보관하며, 보드가 삭제되면 null |
thermal.stated_currents |
전류를 보낸 경우: {"nets": N, "applied": true}. 라우터가 보내주신 값으로 카퍼를 만들었고, 이 점검도 그 넷에는 보내주신 값을(나머지 넷에는 추정값을) 썼다는 뜻입니다 |
thermal.stated_not_checked |
전원 공급 부품이나 부하 부품을 찾지 못해 점검에 쓰지 못한 넷. 직접 확인하세요(라우팅된 보드와 함께 보관하며, 보드가 삭제되면 null) |
thermal.sections_over_by_current_source |
한계를 넘은 구간을 전류 출처별로 나눈 수: {"stated": N, "estimated": N} |
thermal.off_chart |
최대 상승이 IPC-2152 도표 범위를 넘으면(worst_rise_c 1000 이상) true. 실제 온도가 아니라 지나치게 뜨겁다는 뜻입니다 |
thermal.ambient_c, thermal.worst_temp_c |
ambient_c를 보낸 경우: 보내주신 주변 온도와, 주변 온도에 최대 상승을 더한 값(도표 범위를 넘으면 null) |
thermal.currents |
전류를 구한 방법: Estimated from the board's parts. May under-estimate large power stages; confirm high-current nets yourself.(보드의 부품으로 추정한 값입니다. 큰 전력단은 실제보다 낮게 추정할 수 있으니, 대전류 넷은 직접 확인하세요) |
thermal.rails_checked, thermal.rails_not_checked |
추정으로 계산한 전원 넷 수와, 계산하지 못한 넷 수(전원 공급원이나 부하를 찾지 못한 경우) |
shorts |
서로 다른 넷의 카퍼가 닿은 곳(0이 최선). Zap!은 쇼트를 만들지 않으므로, 여기 나온 것은 올리신 보드에 이미 있던 것입니다 |
shorts_already_in_board |
그중 올리신 보드에 이미 있던 쇼트 수(예: 다른 넷 패드 위의 카퍼 그래픽). KiCad에서 고치세요 |
drc_errors, drc_errors_by_type |
보내주신 룰로 돌린 KiCad DRC 에러(위의 drc_errors와 같은 수) |
jlc_violations, jlc_violations_by_type |
JLCPCB 표준 공정으로 만들 수 없는 부분(낮을수록 좋음): jlc_capabilities(1-2 layers 또는 multilayer) 기준 최소 트랙·간격·비아·애뉼러 링·드릴 크기 미달, 보드 가장자리와 홀 간격, 솔더 마스크 슬리버, 카퍼 넥과 슬리버, 그리고 자체 점검 세 가지: via_in_pad(SMD 패드 안의 비아, 충전·캡핑 비아가 필요), acute_angle_acid_trap(90도 미만으로 만나는 트랙), silk_over_copper |
jlc_items |
JLC 위반마다 위치: type, at_mm(x, y), layer, net, refs(관련 풋프린트), description. 모든 항목(최대 2,000개), 종류순. 라우팅된 보드와 함께 보관하며, 보드가 삭제되면 null |
jlc_items_total, jlc_items_truncated |
목록에 들어갈 항목 수, 그리고 목록이 jlc_violations보다 적으면 true |
counts_capped |
KiCad가 199개에서 세기를 멈춘 위반 종류(그 수는 최솟값입니다) |
time_s |
점검에 걸린 초 |
온도 부분은 보드의 부품(레귤레이터, 인덕터, 퓨즈, 페라이트 비드, 각 레일이 공급하는 칩)으로 전원 넷마다 전류를 추정합니다. 데이터시트의 전류 값은 쓰지 않으므로 큰 전력단은 실제보다 낮게 추정할 수 있습니다(프로세서 코어 전원, 모터 브리지, PoE, 모듈 전원 커넥터 등). 그런 넷에서 낮은 값이 나와도 카퍼 폭이 충분하다는 뜻은 아닙니다. 대전류 넷은 모두 실제 부하 전류와 비교하고, 필요하면 카퍼를 넓히거나 푸어를 추가하세요. 높은 값은 "이 넷을 확인하세요"라는 뜻입니다. 작업이 60분 가운데 거의 전부를 이미 썼다면 온도 부분은 건너뜁니다(thermal.computed가 false, reason은 time limit). 공급원과 부하가 있는 전원 레일을 찾지 못하면 reason에 그렇게 나옵니다. 릴리스 2026.10.13.8 이전 작업은 fab: null입니다.
결과 보고서
끝난 작업마다 보고서가 있습니다: GET /v1/jobs/{id}/report.json(키 필요) 또는 작업 상태의 report_url 웹 페이지(키를 입력하면 열림). 항목마다 KiCad에 표시되는 mm 좌표(KiCad 커서 좌표로 바로 찾을 수 있음), KiCad가 알려주는 레이어와 넷, 그리고 해야 할 일(KiCad 명령을 담은 한 문장)이 붙습니다:
- 아직 연결되지 않은 연결(중요 넷 먼저, 양 끝 좌표);
- 보내주신 룰로 돌린 디자인 룰 에러;
- JLC 공정 능력 위반;
- 가장 뜨거운 카퍼 구간 10곳;
- 아무것에도 연결되지 않은 트랙 끝과 비아, 연결되지 않은 카퍼 섬.
카퍼 레이어마다 보드 외곽선과 함께 그린 그림(image.layers, KiCad 자체 플롯 SVG, 모두 같은 틀)도 있으며, 웹 페이지에서는 그림 위에 위치를 표시합니다. 목록마다 최대 2,000개(total과 truncated로 더 있는지 알 수 있음). 보고서는 라우팅된 보드와 함께 보관되며 7일 뒤 또는 DELETE로 삭제됩니다. zap.py run은 라우팅된 보드 옆에 <board>.routed.zap-report.json과 카퍼 레이어마다 <board>.routed.<layer>.svg로 저장하고, zap.py report JOB_ID로도 받을 수 있습니다.
4. 엔드포인트
모든 /v1/jobs 엔드포인트와 POST /v1/check, POST /v1/feedback, /v1/snapshots에는 Authorization: Bearer <api key> 헤더가 필요합니다(Bearer는 대소문자 무관). 기계가 읽을 수 있는 명세는 /openapi.json에 있습니다.
| 메서드와 경로 | 기능 | 성공 응답 |
|---|---|---|
POST /v1/check |
같은 업로드의 라우팅 전 점검: 라우팅 없음, 작업 할당량 사용 안 함(2절) | 200 {"ok", "summary", "board", "blockers", "warnings"} |
POST /v1/jobs |
보드 제출(multipart 업로드, 필드는 2절) | 202 {"id", "status": "queued"} |
GET /v1/jobs |
최근 작업 50개(id, 상태, 시각) | 200 {"jobs": [...]} |
GET /v1/jobs/{id} |
작업 상태(queued/running/succeeded/failed/cancelled), 성공 시 지표, 대기 중 순서(queue_position), 처리한 버전(version) |
200 |
GET /v1/jobs/{id}/result |
라우팅된 보드 다운로드 | 200; 미완료·실패 시 409; 삭제 후 410 |
GET /v1/jobs/{id}/report.json |
결과 보고서: 모든 지적 사항의 위치, 레이어, 넷, 해야 할 일(3절) | 200; 미완료·취소 시 409; 삭제 후 410 |
GET /v1/jobs/{id}/report/{layer}.svg |
카퍼 레이어 하나(F_Cu, B_Cu, In1_Cu ...)와 보드 외곽선 그림 |
200(SVG) |
DELETE /v1/jobs/{id} |
대기 중: 취소(실행되지 않으며 하루 작업 수에 들어가지 않음), 실행 중: 즉시 중지(상태 cancelled, 아무것도 남지 않음), 끝난 작업: 파일 삭제 |
200 {"id", "status", "files_deleted": true} |
POST /v1/feedback |
Zap!이나 결과에 대한 의견을 동의를 받아 Zap! 팀에 보냄(9절) | 201 {"id", "received_at"} |
POST /v1/jobs/{id}/snapshot |
라우팅된 보드의 한 영역(최대 40 mm 정사각형) 그림: 보시고, 동의하시면 피드백에 첨부(9절) | 201 {"snapshot_id", "png_url", "expires_at", "region", "nets_in_region", "issues_marked"} |
GET /v1/snapshots/{id}.png |
그 그림(PNG), 만든 키로만 | 200(PNG) |
DELETE /v1/snapshots/{id}.png |
첨부하지 않을 그림을 바로 삭제 | 200 {"id", "deleted": true} |
GET /v1/health |
서비스 상태 확인, 키 불필요 | 200 {"ok": true} |
GET /v1/status |
대기열, 예상·평소 대기 시간, 라우팅 시간, 라우터 빌드, 키 불필요(웹 페이지: /status) |
200 {"jobs_waiting", "jobs_running", "accepting_jobs", "estimated_wait_s", "typical_wait_s", "typical_run_s", ...} |
GET /v1/version |
서비스 버전과 쓸 수 있는 라우터, 키 불필요 | 200 {"service", "router", "routers": {"stable": "<빌드>", "next": "<빌드>" 또는 null}} |
작업 상태 필드(GET /v1/jobs/{id}):
| 필드 | 의미 |
|---|---|
id |
작업 id |
status |
queued, running, succeeded, failed, cancelled(DELETE로 중지) |
created_at, started_at, finished_at |
UTC 시각(해당 단계 전에는 null) |
queue_position |
대기 중일 때 순서(1이면 다음 차례) |
queue_note |
대기 중이고 순서 말고 다른 이유로 기다릴 때만: minutes가 10을 넘는 작업은 다른 그런 작업이 실행 중이면 기다립니다 |
consent_training |
보내주신 의사 표시(7절) |
keep_existing |
기존 트랙, 비아, 존을 유지한 작업이면 true(2절) |
files_deleted |
라우팅된 보드가 삭제되었으면 true |
report_url, report_json |
끝난 작업: 결과 보고서 웹 페이지(키 입력 필요)와 JSON 경로 |
version |
{"service", "router", "router_choice"}: 이 작업을 처리한 Zap! 릴리스와 라우터 빌드, 그리고 stable과 next 중 무엇을 골랐는지(대기 중에는 null) |
metrics |
성공 시 3절의 수치 |
error |
실패 시 {"code", "message"}(5절) |
작업당 서버 처리 시간(wall_time_s), 2026-10-13 라우터 rc5e1a55, Zap! 2026.10.13.33에서 측정. 처리 시간은 패드 수보다 레이어 수와 밀도에 좌우되고, 대기 시간은 따로 더해지며, 한도는 3,600초입니다.
| 카퍼 레이어 | 패드 | 보드 크기 | 서버 처리 시간, 초(낮을수록 좋음) |
|---|---|---|---|
| 2 | 94 | 89 x 79 mm | 8~14 |
| 2 | 227 | 115 x 75 mm | 24 |
| 2 | 266 | 320 x 81 mm | 90 |
| 2 | 840 | 222 x 132 mm | 512 |
| 2 | 1,880 | 461 x 329 mm | 424 |
| 4 | 322 | 62 x 28 mm | 129 |
| 4 | 347 | 50 x 50 mm | 382 |
| 4 | 707 | 285 x 48 mm | 740 |
| 6 | 767 | 55 x 95 mm | 1,374 |
| 6 | 1,894 | 119 x 29 mm | 2,107 |
OAuth로 연결하기
API 키 대신 Google, GitHub 또는 Microsoft 계정으로 로그인합니다. API 키는 그대로 동작합니다. 로그인하면 인증된 이메일 주소로 Zap! 계정이 만들어집니다(하루 50개 작업, 동시에 2개, 분당 30회 요청. 더 필요하면 hello@mutual.solutions 로 연락하세요). 계정은 두 가지로 씁니다. 계정 페이지에서 API 키를 만들어 다른 키처럼 쓰거나(zap.py, MCP 도구, REST API), 아래처럼 MCP 클라이언트를 OAuth 로그인으로 연결해 키 없이 씁니다.
| 클라이언트 | 방식 | 토큰 대상 | 액세스 토큰 | 갱신 |
|---|---|---|---|---|
| MCP 클라이언트 (Claude Code, Cursor, Codex) | 인가 코드 + PKCE (S256), 자동 등록 | https://zap.mutual.solutions/mcp |
1시간 | 리프레시 토큰, 쓸 때마다 새로 발급. 30일 미사용 또는 로그인 후 90일이 지나면 다시 로그인 |
| 명령줄, 사람 없이 도는 에이전트 | 기기 코드: zap.py login |
https://zap.mutual.solutions/v1 |
1시간 | 위와 같음 |
| 내 서버 (백엔드) | 클라이언트 자격 증명 | https://zap.mutual.solutions/v1 |
1시간 | 새 토큰 요청 |
모든 토큰은 계정의 한도(UTC 하루 작업 수, 동시 작업 수, 분당 요청 수)를 그대로 따릅니다. API 키와 같습니다. 토큰은 발급 대상에서만 동작합니다. MCP 토큰은 /v1에서 거부되고, 반대도 마찬가지입니다. 계정 페이지 /account에서 연결된 앱을 보고 해제할 수 있고, 개발자 자격 증명도 관리합니다.
MCP (원격 서버 https://zap.mutual.solutions/mcp, 도구 check_board, submit_job, get_job, list_jobs, get_report, get_result, delete_job). 보드는 파일 내용 그대로 보냅니다(board, project, rules, 합계 20 MiB). 라우팅된 보드는 임베디드 리소스로 돌아옵니다(최대 8 MiB, 더 크면 zap.py login 후 zap.py download).
# Claude Code: 추가한 뒤 Claude Code에서 /mcp 입력, zap 선택 후 로그인 (브라우저가 열림)
claude mcp add --transport http zap https://zap.mutual.solutions/mcp
// Cursor: ~/.cursor/mcp.json. 저장하면 Cursor가 서버 로그인을 제안함
{"mcpServers": {"zap": {"url": "https://zap.mutual.solutions/mcp"}}}
# Codex: ~/.codex/config.toml 에 아래 두 줄을 넣은 뒤 로그인
# [mcp_servers.zap]
# url = "https://zap.mutual.solutions/mcp"
codex mcp login zap
명령줄.
python3 zap.py login # https://zap.mutual.solutions/device 주소와 코드를 출력: 주소를 열어 코드를 입력하고 승인
python3 zap.py run myboard.kicad_pcb
python3 zap.py logout # 서버에서 로그인 종료
API 키가 없으면 zap.py는 저장된 로그인을 씁니다(~/.config/zap/oauth.json, 본인만 읽을 수 있음). 승인이 끝나면 승인한 계정을 출력합니다. 본인 계정이 아니면 zap.py logout을 실행하세요.
서버 간 연결. /account/credentials에서 클라이언트 ID와 시크릿을 만듭니다(시크릿은 한 번만 표시). 그다음:
# ID와 시크릿은 표준 입력으로 curl에 전달 (bash 내장 printf): 명령줄에 절대 넣지 않음
printf 'user = "%s:%s"\n' "$ZAP_CLIENT_ID" "$ZAP_CLIENT_SECRET" | \
curl -s -K - -d grant_type=client_credentials https://zap.mutual.solutions/oauth/token
# {"access_token": "zat_...", "token_type": "Bearer", "expires_in": 3600, "scope": "zap"}
/v1/... 요청에 API 키처럼 Authorization: Bearer <access_token>으로 보냅니다. 401을 받으면 새 토큰을 받으세요.
| 엔드포인트 | 주소 |
|---|---|
| 서버 메타데이터 (RFC 8414) | GET /.well-known/oauth-authorization-server |
| 리소스 메타데이터 (RFC 9728) | GET /.well-known/oauth-protected-resource/mcp, .../v1 |
| 앱 등록 (RFC 7591) | POST /oauth/register. 리디렉션 URI: http://127.0.0.1, http://[::1], http://localhost(포트 무관) 또는 https:// |
| 인가, 토큰, 해지 (RFC 7009) | GET /oauth/authorize, POST /oauth/token, POST /oauth/revoke |
| 기기 코드 (RFC 8628) | POST /oauth/device_authorization, 페이지 /device |
| 스코프 | zap (하나뿐) |
OAuth 엔드포인트는 아래 에러 표가 아니라 OAuth 에러 형식({"error": "invalid_grant", "error_description": "..."})으로 답합니다. /v1이나 /mcp의 401에는 WWW-Authenticate: Bearer resource_metadata="..."가 붙습니다. 로그인 데이터: 이메일 주소, 제공자 계정 ID, 토큰의 해시. Zap! 서버와 매일 만드는 백업 사본에 보관하며, 백업 사본은 14일 뒤 삭제됩니다 (개인정보 처리방침).
5. 에러
모든 에러는 고정된 코드와, 무엇을 고치면 되는지 한 문장으로 알려주는 메시지를 담은 JSON입니다: {"error": "<code>", "message": "..."}. 실패한 작업은 상태 조회 결과에 {"error": {"code": "...", "message": "..."}}로 나타납니다. 코드는 바뀌지 않으며, 메시지는 더 알기 쉽게 고쳐질 수 있습니다(메시지는 영어입니다).
| 코드 | HTTP | 조치 |
|---|---|---|
unauthorized |
401 | 키를 Authorization: Bearer <key>로 보내고, 빠짐없이 복사했는지 확인하세요. |
rate_limited |
429 | Retry-After 헤더에 적힌 초만큼 기다리세요. |
daily_quota_exceeded |
429 | 00:00 UTC 이후 다시 시도하거나 한도 상향을 요청하세요. |
check_limit_reached |
429 | 이 계정은 오늘의 사전 점검을 모두 썼습니다. 작업을 바로 제출하거나(같은 점검을 합니다) 00:00 UTC 이후 다시 점검하세요. |
server_busy |
503 | 지금 기다리는 작업이 너무 많습니다. Retry-After 초만큼 기다린 뒤 다시 제출하세요(estimated_wait_min이 예상 대기 분). 상태: https://zap.mutual.solutions/status |
too_many_active_jobs |
429 | 대기·실행 중인 작업 하나가 끝날 때까지 기다리세요. |
too_large |
413 | 합계 20 MiB 초과: 보드를 gzip으로 압축해 보내고(같은 board 필드에 board.kicad_pcb.gz, 풀었을 때 64 MiB까지; zap.py, MCP 도구, 웹 페이지는 알아서 압축합니다) .kicad_pcb(와 .kicad_pro)만 보내세요. 그래도 보드 파일 자체가 크면 내장된 3D 모델이나 이미지를 지운 뒤(Footprint Properties > 3D Models) 다시 저장하세요. |
board_too_large_unpacked |
413 | 압축한 보드를 풀면 64 MiB(max_unpacked_bytes)를 넘습니다. 내장된 3D 모델이나 이미지를 지우고(Footprint Properties > 3D Models) 다시 저장해 보내세요. |
invalid_compressed_board |
400 | 보드가 gzip으로 압축된 것처럼 보이지만 풀 수 없습니다(잘렸거나, 손상됐거나, 파일이 여러 개). .kicad_pcb를 gzip으로 다시 압축하거나 압축하지 않고 보내세요. |
length_required |
411 | 업로드에 전체 길이가 없습니다(예: Transfer-Encoding: chunked). 청크 업로드는 지원하지 않습니다. 일반 multipart 업로드로 보내세요(curl -F, Python requests, zap.py가 그렇게 합니다). |
board_required |
400 | 보드를 multipart 필드 board에 넣으세요. |
invalid_board |
422 | KiCad 보드로 읽을 수 없습니다: KiCad PCB Editor에서 보드를 열고 File > Save로 저장한 .kicad_pcb를 올리세요(회로도, 거버, zip 아님). 메시지에 이유가 나옵니다: 보드 외곽선 없음(Edge.Cuts 레이어에서 Place > Draw Lines 또는 Draw Rectangle로 그리세요), 잘린 파일(다시 저장해서 전체를 올리세요), 빈 줄 10,000개 초과(다시 저장하세요). |
invalid_currents |
422 | 전류 파일은 {"nets": {"<넷 이름>": {"amps": A}}} 형식이어야 하며, 넷 이름은 KiCad에 보이는 그대로(Inspect > Net Inspector), 전류는 0~100 A, 넷은 최대 2,000개입니다. 보드에 없는 이름은 unknown_nets에, 마지막 부분이 여러 넷과 겹치는 이름은 후보와 함께 ambiguous_nets에 나옵니다. |
invalid_fab_floor |
422 | fab_floor는 jlc(기본) 또는 board여야 합니다(빼면 jlc). |
fab_floor_unavailable |
409 | 고른 라우터는 아직 보드 자체 최소값을 따를 수 없습니다. fab_floor=jlc로 보내거나, GET /v1/version에 next가 있으면 router=next를 쓰세요. |
invalid_router |
422 | router는 stable 또는 next여야 합니다(빼면 stable). |
router_unavailable |
409 | 지금은 next 라우터를 쓸 수 없습니다. stable을 쓰세요(GET /v1/version의 routers.next 참고). |
invalid_consent |
422 | consent_training은 true 또는 false여야 합니다(빼면 false). |
invalid_keep_existing |
422 | keep_existing은 true 또는 false여야 합니다(빼면 false). |
keep_existing_unavailable |
409 | 고른 라우터는 기존 카퍼를 유지할 수 없습니다. router=stable을 쓰거나, KiCad에서 트랙과 비아를 지우고(Edit > Global Deletions) keep_existing=false로 보내세요. |
invalid_project |
400 | 올바른 KiCad 프로젝트 파일이 아닙니다: PCB Editor에서 File > Save할 때 보드 옆에 저장되는 .kicad_pro를 첨부하거나, 빼고 다시 올리세요. |
invalid_rules |
400 | 커스텀 룰 파일이 올바르지 않습니다: File > Board Setup > Design Rules > Custom Rules에서 저장되는 .kicad_dru(첫 줄이 (version으로 시작)를 첨부하거나, 빼고 다시 올리세요. |
invalid_option |
422 | 라우팅 옵션 값이 올바르지 않습니다(option은 필드, fix는 보낼 값). 고치거나, 빼고 기본값을 쓰세요. |
options_unavailable |
409 | 라우팅 옵션이 아직 켜지지 않았습니다. 옵션을 빼고 기본값으로 보내세요(켜지면 GET /v1/version에 "options": true가 나옵니다). |
footprints_outside_board |
422 | 보드 외곽선 밖에 풋프린트가 있습니다(footprints에 참조 번호와 위치). KiCad에서 보드 안으로 옮기거나(풋프린트를 선택하고 M) 지운 뒤 저장해서 다시 올리세요. 작업은 만들어지지 않고 할당량도 쓰지 않습니다. |
input_has_copper |
422 | 보드에 이미 트랙이나 비아가 있습니다. 지우고 전부 라우팅하려면 strip_existing=true, 유지하고 빠진 연결만 라우팅하려면 keep_existing=true를 보내거나, KiCad에서 지우세요(Edit > Global Deletions, Tracks와 Vias 체크). |
too_many_pads |
422 | 패드 10,000개 초과: 더 큰 보드는 문의하세요. |
too_many_footprints |
422 | 풋프린트 4,000개 초과: 더 큰 보드는 문의하세요. |
board_too_large |
422 | 라우팅 시도 1회당 필요할 것으로 예측되는 메모리가 셀프서비스 한도를 넘습니다(memory에 예측 GB, 외곽 크기, 카퍼 레이어 수, 그리드 셀 수). 이 정도 크기의 보드는 hello@mutual.solutions로 메일 주시면 기업(B2B) 작업으로 진행해 드립니다. 제출 단계에서 거절되면 작업이 만들어지지 않고 할당량도 쓰지 않습니다. 전원 레일 전류까지 따진 작업 안의 예측이 넘으면 작업이 라우팅 전에 이 코드로 실패합니다. |
unsupported_layer_count |
422 | 카퍼 레이어는 2~16층이어야 합니다: File > Board Setup > Board Stackup > Physical Stackup의 Copper layers에서 층수를 정하고 저장하세요. |
not_found |
404 | 없는 작업 id, 다른 키로 제출한 작업, 또는 없는 주소입니다(주소 목록은 4절). |
not_ready |
409 | 아직 대기·실행 중입니다. 계속 조회하세요. |
no_result |
409 | 작업이 실패했습니다. 상태의 에러를 확인하세요. |
job_cancelled |
409 | 작업이 취소되어 라우팅된 보드가 없습니다. 필요하면 보드를 다시 제출하세요. |
expired |
410 | 라우팅된 보드가 삭제되었습니다(7일 경과 또는 요청). 다시 제출하세요. |
method_not_allowed |
405 | 그 주소는 이 메서드를 받지 않습니다. 4절을 보세요. |
bad_request |
400 | 요청을 읽을 수 없습니다. 1절처럼 multipart로 보내세요. |
route_failed |
(작업) | 라우터가 결과를 내지 못했습니다: KiCad에서 Edge.Cuts 외곽선이 닫혀 있고 모든 풋프린트가 그 안에 있는지 확인하고(Inspect > Design Rules Checker가 둘 다 보여줍니다), 반복되면 작업 id를 보내주세요. |
rules_check_failed |
(작업) | KiCad가 보내주신 룰로 보드를 검사하지 못했습니다: 이 보드와 함께 저장한 .kicad_pro(와 .kicad_dru)를 보내거나(File > Save, 룰은 File > Board Setup > Design Rules), 빼고 KiCad 기본 룰로 올리세요. |
unsupported_rules |
(작업) | .kicad_dru에 Zap!이 아직 적용하지 못하는 룰 조건(예: insideCourtyard())이 있습니다: File > Board Setup > Design Rules > Custom Rules에서 그 룰을 지우거나 단순하게 고치거나, .kicad_dru를 빼고 다시 제출하세요. |
output_has_shorts |
(작업) | 모든 라우팅 시도에서 넷 간 쇼트가 남아 결과를 돌려드리지 않습니다: 보드와 함께 저장한 .kicad_pro(File > Save)를 보내 Zap!이 보내주신 룰로 라우팅하게 하고, 반복되면 작업 id를 보내주세요. |
timeout, timeout_or_limit |
(작업) | 시간 또는 메모리 한도에 걸렸습니다. 작업 id를 보내주세요. |
check_busy |
503 | 라우팅 전 점검이 동시에 많이 실행 중입니다. Retry-After 초만큼 기다렸다가 다시 점검하세요. |
claim_invalid |
404 | 키 수령 링크가 올바르지 않습니다. 링크 전체를 복사했는지 확인하거나 새 링크를 요청하세요. |
claim_used |
410 | 이미 수령한 키입니다(키는 한 번만 보여드립니다). 새 키를 요청하세요. |
claim_expired |
410 | 키 수령 링크가 만료되었습니다(72시간). 새 링크를 요청하세요. |
feedback_needs_user_approval |
400 | 피드백은 사용자 동의가 있어야 보냅니다(user_approved가 true가 아님). 보낼 문구 그대로를 사용자에게 보여주고 보낼지 물어본 뒤, 동의할 때만 user_approved: true로 보내세요. |
feedback_message_length |
400 | 피드백 메시지가 비었거나 4,000자를 넘습니다. 사용자의 말을 1~4,000자로 보내되, 줄이려면 사용자 동의를 받으세요. |
invalid_feedback |
400 | 피드백 요청이 올바르지 않습니다(field에 첫 문제). message(글), user_approved: true, 그리고 필요하면 job_id, kind(praise, problem, idea, other), contact_ok(true/false)만 담은 JSON을 보내세요. |
feedback_limit_reached |
429 | 이 키는 오늘 보낼 수 있는 피드백(UTC 하루 20건)을 다 썼습니다. 00:00 UTC 이후에 보내거나 hello@mutual.solutions로 메일을 보내세요. |
snapshots_off |
403 | 이 키는 보드 그림이 꺼져 있습니다(플랫폼이 자기 고객을 위해 쓰는 키는 고객의 보드라서 꺼 둡니다). 피드백을 글로만 보내세요. |
invalid_snapshot |
400 | 그림 요청이 올바르지 않습니다(field에 첫 문제). x_mm, y_mm(영역 중심, KiCad에 보이는 mm 좌표), 그리고 필요하면 size_mm(1~40), layers(F.Cu 같은 카퍼 레이어 이름), show_issues(true/false)만 담은 JSON을 보내세요. |
snapshot_too_large |
400 | 그림 영역이 40 mm보다 큽니다(그림은 보드 전체가 아니라 한 지점만 보여줍니다). size_mm을 40 이하로, 그 지점을 중심으로 보내세요(보고서의 at_mm 위치가 도움이 됩니다). |
snapshot_outside_board |
422 | 그림 영역이 보드 밖입니다(board_mm에 보드 범위). x_mm, y_mm을 보드 위 한 지점(예: 보고서의 위치)으로 맞추세요. |
snapshot_limit_reached |
429 | 이 키는 오늘 만들 수 있는 보드 그림(UTC 하루 30장)을 다 썼습니다. 00:00 UTC 이후에 만들거나 피드백을 글로만 보내세요. |
snapshot_not_found |
404 | 이 키와 작업으로 쓸 수 있는 그 id의 그림이 없습니다(피드백에 첨부하지 않은 그림은 만든 뒤 24시간에 삭제되며, 그림 하나는 피드백 하나에만 첨부됩니다). POST /v1/jobs/{id}/snapshot으로 새로 만드세요. |
tryit_limit_reached |
429 | 이 네트워크 주소는 오늘의 무료 체험 작업을 모두 썼습니다. 00:00 UTC 이후에 다시 시도하거나 https://zap.mutual.solutions/auth/login 에서 로그인하고 계정 페이지에서 API 키를 만드세요. |
tryit_board_too_large |
422 | 무료 체험에는 너무 큰 보드입니다(이 응답의 max_pads보다 패드가 많거나 외곽이 매우 큼). 더 작은 보드로 시도하거나 https://zap.mutual.solutions/auth/login 에서 로그인하고 계정 페이지에서 API 키를 만드세요. |
tryit_layers_unsupported |
422 | 무료 체험은 카퍼 2층 또는 4층 보드만 라우팅합니다. 그런 보드로 시도하거나 https://zap.mutual.solutions/auth/login 에서 로그인하고 계정 페이지에서 API 키를 만드세요. |
tryit_busy |
503 | 무료 체험이 지금 바쁩니다(다른 방문자의 보드를 라우팅 중이거나 오늘의 체험 작업이 모두 쓰임). Retry-After 초 뒤에 다시 시도하거나 https://zap.mutual.solutions/auth/login 에서 로그인하고 계정 페이지에서 API 키를 만드세요. |
tryit_human_check_failed |
403 | 사람 확인을 통과하지 못했거나 만료됐습니다. 페이지에서 다시 확인한 뒤 버튼을 한 번 더 누르세요. |
tryit_not_found |
404 | 이 id와 토큰에 맞는 무료 체험 작업이 없습니다. 토큰은 업로드에 대한 응답에서 한 번만 주어지므로, 같은 페이지에서 작업을 확인하세요. |
invalid_access_request |
422 | 요청이 빠졌거나 올바르지 않습니다(field가 첫 번째 문제를 알려줍니다). 254자 이하의 올바른 이메일, 1~2,000자의 사용 목적, 200자 이하의 이름이나 회사를 적고 동의 상자를 체크한 뒤 다시 보내세요. |
access_requests_closed |
503 | 지금은 이 양식으로 접근 요청을 더 받지 않습니다. hello@mutual.solutions로 이메일을 보내세요. |
internal |
500 또는 (작업) | 저희 쪽 문제입니다. 다시 시도하고, 반복되면 작업 id를 보내주세요. |
작업은 완전히 라우팅된 보드를 돌려주거나 실패하거나 둘 중 하나입니다. 부분 결과는 없습니다.
점검 중(보통 1분 이내)에는 Zap! 앞단의 네트워크가 JSON 대신 HTML 페이지로 502를 돌려줄 수 있습니다. 1분쯤 기다렸다가 다시 시도하세요. zap.py는 상태 조회와 다운로드를 스스로 다시 시도합니다.
error code: 1010이라는 글자와 함께 오는 403은 Zap!이 아니라 Zap! 앞단의 네트워크(Cloudflare)가 보낸 것입니다. 아무 User-Agent 헤더나 보내세요(예: my-agent/1.0). Python 기본 urllib이 보내는 기본 이름은 앞단에서 막힙니다.
6. 한도
| 항목 | 값 |
|---|---|
| 작업당 업로드 크기 | 보낸 크기 합계 20 MiB(20,971,520바이트) |
| 압축한 보드를 풀었을 때 크기 | 64 MiB(67,108,864바이트) |
| 보드당 패드 | 10,000개 |
| 보드당 풋프린트 | 4,000개 |
| 카퍼 레이어 | 2~16층 |
| 요청 | 키당 60초 동안 30회, 상태 조회 포함(5초 간격으로 조회하세요) |
| 라우팅 전 점검 | 키당 한 번에 하나, 하루 작업 수에 들어가지 않음 |
| 작업 | 키 발급 시 정한 값(키 수령 페이지에 표시). 기본은 UTC 하루 50개, 대기·실행 중 동시 2개 |
| 작업 1건 | 최대 60분 |
| 서비스 전체 | 한 번에 기다릴 수 있는 작업 수에 한도가 있습니다. 대기열이 차면 POST /v1/jobs가 503 server_busy와 Retry-After, estimated_wait_min(예상 대기 분)을 돌려줍니다. 대기열과 평소 대기 시간은 zap.mutual.solutions/status에서 볼 수 있습니다 |
| 키 없이 여는 페이지와 다운로드 | IP 주소당 60초 동안 120회 |
| 피드백 | 키당 UTC 하루 20건, 건당 4,000자, 그림 최대 3장 |
| 보드 그림 | 키당 UTC 하루 30장, 최대 40 mm 정사각형, 라우팅된 보드를 보관하는 동안(7일)만; 피드백에 첨부하지 않으면 24시간 뒤 삭제 |
7. 데이터 처리
개인정보처리방침(English)과 이용약관(영문)이 적용됩니다. 요약하면:
- 보내주신 파일은 해당 작업을 라우팅하는 데만 씁니다.
- 업로드 파일은 작업이 끝나는 즉시 삭제합니다. 라우팅된 보드는 7일 뒤에 삭제되며,
DELETE /v1/jobs/{id}로 바로 지울 수도 있습니다. - 작업 기록(id, 시각, 상태, 요약 수치)은 보드 데이터 없이 2년간 남깁니다.
- 서면 동의 없이 보드를 저희 소프트웨어의 학습이나 개발에 쓰지 않습니다.
consent_training=true는 의사를 기록할 뿐이며, 그것만으로 서면 동의가 되지는 않습니다. - 요청 로그에는 시각, IP 주소, 키 id, 요청 경로, 상태, 크기만 남습니다. 보드 내용, 파일 이름, 키, 피드백 내용은 절대 남기지 않습니다.
- API 키는 단방향 해시로만 저장합니다. 키를 잃어버리면 새로 발급해 드립니다.
- 피드백. 직접, 또는 동의하신 AI 에이전트를 통해 피드백을 보내시면, 보내신 글과 첨부하신 보드 그림만 Zap! 개선에 씁니다. 이것은 작업 id, 그리고 저희가 이미 보관하는 요약 수치와 연결되며, 보드 파일은 보관하지도 쓰지도 않습니다. 피드백은 Zap! 팀이 AI 도구의 도움을 받아 읽습니다. 여쭙지 않고 공개하거나 인용하지 않고, 판매하지 않으며, AI 모델 학습에 쓰지 않습니다. 연락을 허락하신 경우에만 답장을 드립니다. 최대 2년, 또는 hello@mutual.solutions로 삭제를 요청하실 때까지 보관하며, 백업 사본은 14일 안에 만료됩니다. 개인 정보나 기밀은 빼고 보내주세요.
- 작업 기록, 키 해시, 피드백, 로그인 계정(보드 데이터 없음)은 매일 서비스와 같은 클라우드 리전의 암호화된 비공개 저장소에 백업하며, 각 백업 사본은 14일 뒤 삭제합니다.
8. 이 (아직) 하지 않는 것
- 부품 배치: 부품은 배치하신 자리에 그대로 있습니다.
- 회로도 검사.
- 차동쌍 스큐 외의 길이 매칭, 임피던스 보장.
- 제조 승인(sign-off). 제작 전에 모든 보드를 검토하고 직접 DRC를 돌리세요.
9. 피드백
무엇을 그대로 썼는지, 무엇을 고쳤는지, 무엇을 버렸는지 알려주세요. 짧은 메모는 언제든 환영합니다. 보내는 방법은 두 가지입니다.
- AI 에이전트나 명령줄로. Zap!을 쓰는 에이전트에게 Zap!이나 결과에 대한 생각을 말씀하시면, 에이전트가 저희에게 보낼지 묻고 보낼 문구 그대로와 작업 id를 보여드린 뒤, 동의하실 때만 보냅니다. 명령줄:
python3 zap.py feedback "U3 근처 라우팅이 지저분해요" --job JOB_ID는 문구를 보여주고 먼저 묻습니다. API: 키와 함께POST /v1/feedback에 JSON{"message": "...", "user_approved": true, "job_id": "...", "kind": "problem", "contact_ok": true}를 보내세요.kind는praise,problem,idea,other중 하나이고,contact_ok: true면 계정 이메일로 답장을 드릴 수 있습니다. 응답은201 {"id", "received_at"}입니다. 최대 4,000자, 키당 UTC 하루 20건이며, 보드 파일은 이 방법으로 보내지 않습니다. - 그 지점의 그림. 피드백이 보드의 한 곳에 관한 것이면, 에이전트가 그 영역만 담은 그림(최대 40 mm 정사각형, 보고서의 빠진 연결과 디자인 룰 오류 표시)을 보여드리고, 동의하시면 첨부합니다:
POST /v1/jobs/{id}/snapshot에{"x_mm": X, "y_mm": Y, "size_mm": 20}을 보낸 뒤 피드백의"snapshot_ids"에 넣습니다(최대 3장). 명령줄:python3 zap.py snapshot JOB_ID --at 93.5,69.5로 그림을 저장하고,zap.py feedback "..." --job JOB_ID --snapshot ID로 첨부합니다. 첨부하지 않은 그림은 24시간 뒤 삭제되며, 원하지 않으시면 바로 지웁니다(DELETE /v1/snapshots/{id}.png). 보드 전체는 절대 아닙니다. - 이메일로 hello@mutual.solutions에 보내주세요. 수정한
.kicad_pcb도 보내주실 경우, Zap! 개선에 써도 되는지 같은 메시지에 적어주세요. 서면 동의가 없으면 답변에만 사용하고 그 뒤 삭제합니다.
AI 에이전트에게. 사용자가 Zap!이나 라우팅 결과에 대해 의견(마음에 든 점, 문제, 바라는 점)을 말할 때마다, Zap! 팀에 보낼지 한 번 물어보세요. 물을 때는 어떻게 되는지 부풀리거나 줄이지 말고 짧은 한 문장으로 알려주세요. 예: "이 내용을 Zap! 팀에 보낼까요? 이 글(그림이 있으면 그림도)만 Zap! 개선에 쓰이고 보드 파일은 쓰이지 않으며, 삭제를 요청할 수 있습니다." 보낼 문구 그대로(사용자의 말, 또는 사용자가 동의한 요약)와 작업 id를 보여주세요. 동의할 때만 send_feedback이나 POST /v1/feedback으로 보내세요. 거절하면 그 세션에서는 다시 묻지 마세요. 피드백이 보드의 한 곳에 관한 것이면, 보고서의 문제 위치로 맞춘 그 영역만의 그림(board_snapshot 또는 POST /v1/jobs/{id}/snapshot)을 첨부할지 제안하세요. 보내기 전에 그림을 사용자에게 보여주고, 동의할 때만 첨부하세요. 원하지 않으면 그림을 지우세요(delete_snapshot). 보드 전체는 절대 안 됩니다. 사용자가 동의하지 않은 보드 파일이나 개인 정보는 절대 넣지 마세요.