앱 개발자 가이드
스토어에 올릴 앱을 만드는 개발자를 위한 안내입니다. 앱은 여러분의 서버에서 돌고, TableRun 이 서명한 HTTP 요청으로 그 서버를 부릅니다.
시작하는 순서
store.tablerun.biz의 개발자 콘솔에서 개발자 등록을 합니다 (승인 후 앱을 만들 수 있습니다).- 앱 등록 — 이름·분류·실행 방식·설정 항목·요금을 정하고 앱 서버 주소(
baseUrl)를 넣습니다. 이때 HMAC 시크릿이 한 번만 표시됩니다. - 아래 계약대로 앱 서버에
/tablerun/pull또는/tablerun/push를 구현합니다. - 콘솔에서 심사 제출 → 승인되면 스토어에 공개됩니다.
- 매출과 정산 예정 금액은 콘솔의 앱 상세에서 확인합니다.
필요한 앱을 직접 만들기 어렵다면 맞춤 제작을 요청할 수 있습니다.
0. 한눈에
[TableRun] --(HMAC 서명 POST)--> https://{개발자 서버}/tablerun/pull (direction=in — 테이블에 쓰는 앱)
[TableRun] --(HMAC 서명 POST)--> https://{개발자 서버}/tablerun/push (direction=out — 행을 내보내는 앱)
[TableRun] --(HMAC 서명 POST)--> https://{개발자 서버}/tablerun/validate (선택 — 앱 설치 설정 검증. 설정 저장 시 실제로 호출)
[TableRun] --(HMAC 서명 GET) --> https://{개발자 서버}/tablerun/health (선택 — 심사/모니터링)
- 우리가 부른다. 개발자 서버는 inbound HTTPS 를 받고 서명을 검증하면 된다. 언어·프레임워크 자유.
- 앱 등록 시
baseUrl과 HMAC 시크릿(1회 표시)을 받는다. TableRun 은 시크릿을 암호화 보관하고 평문을 다시 보여주지 않는다. - 사용자 워크스페이스의 데이터가 개발자 서버로 전달된다. 스토어의 앱 상세·신청 화면에 그 사실과 대상 host 가 표시된다.
1. 인증 — HMAC 서명
모든 호출에 헤더 4개가 붙는다.
| 헤더 | 값 |
|---|---|
X-TableRun-App |
앱 키 (app_key, 예 acme-orders) |
X-TableRun-Timestamp |
호출 시각 unix 밀리초 (문자열) |
X-TableRun-Nonce |
호출마다 새 랜덤 32hex |
X-TableRun-Signature |
v1= + hex(HMAC-SHA256(secret, canonical)) |
canonical = timestamp + "." + nonce + "." + METHOD + "." + path + "." + sha256hex(body)
METHOD는 대문자,path는 baseUrl 뒤의 경로만 (/tablerun/pull— 쿼리스트링 없음),body는 전송된 바이트 그대로 (GET 은 빈 문자열의 sha256).- 검증: (1)
|now - timestamp| ≤ 5분(2) nonce 를 5분간 기억해 재사용 거부 (3) 서명 상수시간 비교. 서버가 여러 노드면 nonce 저장소를 Redis/DB 등 공유 스토어로 바꿀 것 — 노드마다 따로 기억하면 다른 노드로 온 리플레이를 막지 못한다. - 실패는
401로 응답한다. TableRun 은 401/403 을 설정 오류로 보고 재시도하지 않는다.
시크릿은 32바이트 base64url (43자). 회전은 개발자 콘솔에서 새로 발급(구 시크릿은 즉시 무효).
2. 공통 규칙
- 본문
Content-Type: application/json; charset=utf-8. 응답도 JSON. - 타임아웃: 연결 5초, 전체 30초. 응답 본문 상한 1MB. 넘으면 실패 처리.
- 재시도: 5xx·네트워크 오류는 설치의
retry_max만큼 (기본 0). 4xx 는 재시도하지 않는다. baseUrl은 공인 호스트만 (사설 IP·메타데이터·localhost 차단 — SSRF 가드). 운영은 https 필수.- 포트는 80 / 443 / 8080 / 8443 만 허용된다 (그 외 포트는 호출 자체가 거절 ).
- 리다이렉트는 따르지 않는다.
- 행 값의 표현은 TableRun 외부 API 와 같다 (컬럼 타입 12종,
api_name키 — 테이블의 API 탭 참고).
3. POST /tablerun/pull — 가져오기
요청:
{
"workspaceId": "wsHq2mAUUvxfkmXt",
"installId": "instZKraGXCC39Ohn0",
"config": { "storeId": "12345", "since": "2026-08-01" },
"cursor": null,
"limit": 500
}
config= 사용자가 앱 설치 화면에서 입력한 값 (앱 등록 시 선언한configFields기준).cursor= 이전 응답의nextCursor(첫 호출 null). 한 번에limit행 이하.input(선택) = API 실행(POST /v1/apps/{installId}/collect, )의 입력이 첫 페이지에만 실린다 — 예{"url": "...", "source": "<html>…", "collectedAt": "…"}. 입력이 없는 수동·주기 실행은 종전과 같다. 앱은 입력이 있으면 그 입력으로 행을 만들고(예: 페이지 HTML 파싱 → 1행), 없으면schema만 돌려줘도 된다(테이블 컬럼이 비어 있으면 그때 채워진다).
응답:
{
"schema": [
{ "apiName": "order_no", "displayName": "주문번호", "type": "TEXT", "isUnique": true, "isRequired": true },
{ "apiName": "amount", "displayName": "금액", "type": "INTEGER" },
{ "apiName": "ordered_at", "displayName": "주문시각", "type": "DATETIME" }
],
"keyColumn": "order_no",
"rows": [
{ "order_no": "A-1001", "amount": 12000, "ordered_at": "2026-08-28T01:00:00.000Z" }
],
"nextCursor": "eyJwYWdlIjoyfQ"
}
schema는 매 응답에 전체를 보낸다 (TableRun 이 대상 테이블을 처음 만들 때 쓰고, 이후엔 검증만).type은TEXT|LONG_TEXT|INTEGER|DECIMAL|BOOLEAN|DATE|DATETIME|SELECT|MULTI_SELECT|URL|EMAIL|JSON.keyColumn은isUnique: true인 컬럼이어야 한다 — TableRun 은 이 키로 upsert 한다 (재실행 = 갱신).- 행의 식별자가 여러 컬럼의 조합이면
keyColumn대신"keyColumns": ["site", "product_no"](2~5개, 전부 schema 에 있어야 함). TableRun 이 테이블에 조합 유니크 키를 만들고(빈 테이블은 첫 가져오기 때, 이미 컬럼이 있는 테이블은 앱 설정의 "구조 적용" 때) 그 조합으로 upsert 한다. 부분 컬럼 각각은 유니크가 아니어도 된다. 예: 쇼핑몰 상품정보 수집(사이트 + 상품번호). rows≤limit.nextCursor가 있으면 TableRun 이 이어서 부른다 (같은 실행 안, 최대 20페이지).- 값 검증은 TableRun 의 값 의미론(계약)으로 하며 실패 행은 결과 리포트의
failed로 집계된다.
3b. POST /tablerun/schema — 현재 테이블 구조 (선택)
요청 {workspaceId, installId, config} → 응답 {schema[], keyColumn} 또는 {schema[], keyColumns[]} (pull 응답의 키 필드와 같은 모양).
TableRun 앱 설정 화면이 앱이 만드는 컬럼이 바뀌었는지 감지하고(없는 컬럼·유니크 키·순서·타입·앱이 더 안 쓰는 컬럼) 사용자가 "구조 적용"을 누르면
없는 컬럼을 추가하고 앱 순서로 정렬한다(타입이 다른 컬럼·앱이 더 안 쓰는 컬럼은 지우거나 바꾸지 않는다 — 옛 필수만 해제).
이 경로가 없으면(404) TableRun 은 pull(limit 1, input 없음)의 schema 로 대신한다 — 그래서 가져오기 앱은 입력이 없을 때도 스키마만은 돌려줘야 한다.
4. POST /tablerun/push — 보내기
요청:
{
"workspaceId": "wsHq2mAUUvxfkmXt",
"installId": "instMT4NxdWBTpGyhJ",
"config": { "channel": "orders" },
"run": { "runId": "runaLrWWkC8okEzIP", "trigger": "manual" },
"rows": [
{ "rowId": "rowbOPpH9UHRp1FDo", "data": { "order_no": "A-1001", "amount": 12000 } }
]
}
응답:
{
"results": [
{ "rowId": "rowbOPpH9UHRp1FDo", "ok": true, "message": "created #55" }
]
}
- v1 은 행 1건씩 보낸다 (
rows.length === 1). 배치 전송은 v2. ok: false면message가 run_items 의 오류 메시지가 된다 (최대 500자).- 응답이 없거나 형식이 틀리면 그 행은
FAILED(REMOTE_BAD_RESPONSE).
5. POST /tablerun/validate — 설정 검증 (선택)
요청 { "workspaceId", "config" } → 응답 { "ok": true } 또는 { "ok": false, "issues": [{ "field": "storeId", "message": "..." }] }.
구현하지 않으면(404) TableRun 은 configFields 의 형식 검증만 한다.
6. GET /tablerun/health — 헬스 (선택)
응답 { "ok": true, "version": "1.2.0" }. 심사 시 1회, 이후 스토어 상태 표시용으로 주기 호출할 수 있다.
7. 앱 등록 시 선언하는 것 (remote_config)
{
"baseUrl": "https://apps.acme.com",
"capabilities": ["pull"],
"configFields": [
{ "key": "storeId", "label": "스토어 ID", "type": "text", "required": true },
{ "key": "since", "label": "시작일", "type": "date" }
]
}
capabilities는 앱의direction과 일치해야 한다 (in → pull, out → push).direction은 실행 방식일 뿐이다 — 스토어는category(messaging | commerce | data | integration | other, 앱 특성) 로만 진열하고 방향을 사용자에게 보이지 않는다.category는 등록·수정 폼(및app.json)의 필수 값.configFields.type=text | secret | number | date | select(options[]) | column | template.secret은 TableRun 이 암호화해 저장하고(화면은 마스크) pull/push/validate 호출 직전에만 평문으로 넣어 준다.POST /tablerun/validate는 사용자가 설치 설정을 저장할 때 TableRun 이 실제로 부른다(설정이 비어 있지 않을 때). 응답{ok:false, issues:[{field,message}]}면 저장이 거절되고 사용자에게field: message가 보인다. 구현하지 않으면(404) 통과. 자격증명 확인(예: 알리고 잔여 건수 API)은 여기서 한다 — 발송 같은 부작용은 넣지 않는다.column= 사용자가 설치에 붙은 테이블의 컬럼을 고른다 (값 = 컬럼 api_name, TableRun 이 실존 컬럼인지 검증).template={{api_name}}을 넣는 여러 줄 텍스트 — 화면이 컬럼 삽입 버튼과 첫 행 기준 미리보기를 붙여 준다 (값 그대로 전달, 렌더는 앱이).hint(≤200자) 를 주면 입력 아래 한 줄 안내로 보인다.- 예: 알리고 SMS 발송 앱 — 알리고 사용자 ID(text) · API Key(secret) · 발신번호(text) · 받는 사람(select) · 수신번호 컬럼(column) · 메시지 템플릿(template) · LMS 제목(template) · 테스트 모드(select).
sites= 수집 대상 사이트 다중 선택 (값 = 사이트 코드 배열). 선택지는 앱별 카탈로그 (code·이름·host·path·국가·카테고리·공개·사용) — 사이트 카탈로그 편집은 현재 TableRun 에 요청해 등록한다. 저장 시 enabled 코드만 허용. 확장은GET /v1/app-token의sites[]{code,name,host,path}로 대상 페이지를 판정하고collect에siteId를 실어 보내며,pull의input.siteId로 받는다.
scopes(선택) = 앱 토큰 범위. 선언하면 설치 화면에 "앱 토큰" 발급이 열리고, 사용자 기기에서 도는 앱(크롬 확장)이 그 토큰으로 §5.9 API 를 부른다. 값:collect(수집 실행) ·runs:read(실행 상태) ·tables:read·rows:read·rows:write— 전부 설치가 붙은 테이블 한정. 사용자는 범위를 고르지 않는다 — 선언이 곧 범위다.devApis(선택) = 앱 서버가 부를 TableRun 개발자 API(§10). 값:parse.detail. 선언한 앱만 콘솔에서 개발자 API 키(trdev_)를 발급할 수 있다.extension(선택) = 함께 배포하는 크롬 확장{name, downloadUrl?(https:// 또는 /downloads/…zip), storeUrl?, version?, howTo?}. 선언하면 사용자의 앱 설정 화면에 "크롬 확장 프로그램" 섹션(내려받기/웹스토어 링크 + 설치 4단계 + howTo)이 뜬다. 콘솔에서는 scopes 를 하나라도 고르면 입력란이 열린다.- 요금(
pricing) —remote_config가 아니라 앱 자체 속성이지만 같은 폼에서 정한다. 모델 3종:{"model":"free"}/{"model":"monthly","amountKrw":9900}/{"model":"metered","unit":"saved_rows","tiers":[{"code":"S","name":"스타터","amountKrw":9900,"included":1000}, …]}.metered= 월정액 티어 + 한 달 포함 건수. 티어 15개, 코드20자) 유일, 요금·포함 건수 오름차순. 첫 티어가 설치 기본값.A-Z0-9(1unit은 실행 방식이 정한다 — pull 앱(in)saved_rows(TableRun 이 테이블에 쓴 행: 새 행 + 갱신 행), push 앱(out)sent_rows(SUCCESS 항목). 다른 값은 거부.- 개발자가 부를 과금 API 는 없다. 계량은 TableRun 이 실행 결과를 보고 하고, 청구·티어 변경·정산도 플랫폼이 한다. 앱 서버가 "N건 처리했다"고 보고해도 요금에는 쓰이지 않는다.
- 한도에 닿으면 가져오기는 남은 건수만 저장하고(
quotaSkipped), 수집 API(/v1/apps/{id}/collect)와 보내기 실행은APP_QUOTA_EXCEEDED(429) 로 거절된다. 사용자는 앱 설정에서 티어를 올린다. - 개발자 콘솔 앱 상세에 이번 달 매출·구독 수·정산 예정(매출 × 플랫폼 정산 비율, 기본 80%)이 보인다. 지급은 월말 정산.
- 달이 끝나면 TableRun 이 개발자별 금액을 확정하고 계좌이체로 지급한다. 콘솔 첫 화면 정산 내역에 월별 순매출·지급액·상태(지급 대기 / 지급 완료와 날짜)가 보인다. 환불된 결제는 순매출에서 빠진다.
- 개발자 콘솔이 곧 이 선언의 입력 폼이다 (앱 등록·수정 화면): 설치 설정 항목 편집기(타입 7종·필수·안내·select 선택지), 요금 편집기(무료/월정액/사용량 티어), 앱 토큰 범위·개발자 API 체크박스. 스토어 상세의 데이터 전달 고지가 같은 값을 보여 준다.
8. 사용자 쪽에서 도는 앱 (크롬 확장) — 앱 토큰
확장은 사용자의 브라우저에서 돌고 TableRun 을 부른다. 두 가지 모양이 있다:
- remote + scopes (권장): 서버가 있는 앱에 확장을 곁들인다. 확장이 보낸 것을 서버가 처리한다(수집 =
collectscope +pull의input). 예: 쇼핑몰 상품정보 수집(product-collect). - extension 전용 (
runtime=extension, 서버 없음): 확장이 앱 토큰으로 붙은 테이블의 행을 직접 읽고 쓴다. scopes 는tables:read·rows:read·rows:write만 (수집·실행 조회는 서버가 있어야 뜻이 있다 — 콘솔이 막는다).
remote 앱에 확장을 붙이는 흐름:
- 사용자가 앱을 설치하고 앱 설정 화면 앱 토큰에서 토큰(
trapp_…)을 발급받아 확장 옵션에 붙여 넣는다 (1회 표시, 설치당 활성 5개, 폐기 즉시 효력). - 확장은
GET /v1/app-token으로 연결을 확인하고(설치·테이블 이름), 상품 페이지에서POST /v1/apps/{installId}/collect {url, source}를 부른 뒤GET /v1/runs/{runId}를 폴링한다 (아래 "앱 토큰으로 부르는 API"). - TableRun 워커가 앱 서버
/tablerun/pull을input = {url, source, collectedAt}과 함께 부른다 (§3). 앱 서버가 파싱·매핑해 행을 돌려주면 유니크 키로 upsert 된다.
확장에는 TableRun 주소와 앱 토큰만 둔다 — 개발자 API 키와 HMAC 시크릿은 앱 서버에만 둔다.
9. 개발자 API /v1/dev/* — 앱 서버가 TableRun 을 부른다
앱 서버가 TableRun 이 제공하는 처리 엔진(상품 페이지 파싱 등)을 쓰고 싶을 때 부른다. 인증은 개발자 API 키 Authorization: Bearer trdev_…
(앱 1개당 1개 — 콘솔 store.*/developer 앱 화면에서 발급/회전, remote_config.devApis 를 선언한 remote 앱만). HMAC 시크릿과 다른 축이다 — 시크릿은 선을 타지 않는다.
POST /v1/dev/parse/detail # {"url": "https://…", "source": "<html>…", "siteId": "coupang"?} → {code:"200", data:{items:{…}}}
- 초당 5회(앱 단위),
source4MB. 선언하지 않은 API → 403, 키 형식/미발급 → 401. 응답의 상품 정보는data.items.itemName/price/stock/image/description/options…에 담긴다.
앱 토큰으로 부르는 API
앱 토큰(trapp_…)은 Authorization: Bearer 헤더에 넣습니다. 주소는 https://api.tablerun.biz/v1.
| 호출 | 필요한 범위 | 설명 |
|---|---|---|
GET /v1/app-token |
(모든 토큰) | 연결 확인 — 설치·테이블 이름, 허용 범위, 수집 대상 사이트 |
POST /v1/apps/{installId}/collect |
collect |
수집 실행 접수 {url, source, siteId?} → {runId} |
GET /v1/runs/{runId} |
runs:read |
실행 상태·결과 |
GET /v1/tables/{tableId} · /rows |
tables:read · rows:read |
붙은 테이블의 구조·행 조회 |
POST/PATCH/DELETE /v1/tables/{tableId}/rows… |
rows:write |
붙은 테이블의 행 쓰기 |
- 토큰당 초당 호출 수는 워크스페이스 플랜이 정합니다. 넘으면
429 RATE_LIMITED와Retry-After. - 앱의 한 달 포함 건수를 다 쓰면
429 APP_QUOTA_EXCEEDED— 사용자가 앱 설정에서 티어를 올려야 합니다.
문의
계약·심사·정산에 관한 문의는 문의하기에서 분류를 스토어로 골라 남겨 주세요.