TableRun 매뉴얼

앱 개발자 가이드

스토어에 올릴 앱을 만드는 개발자를 위한 안내입니다. 앱은 여러분의 서버에서 돌고, TableRun 이 서명한 HTTP 요청으로 그 서버를 부릅니다.

시작하는 순서

  1. store.tablerun.biz 의 개발자 콘솔에서 개발자 등록을 합니다 (승인 후 앱을 만들 수 있습니다).
  2. 앱 등록 — 이름·분류·실행 방식·설정 항목·요금을 정하고 앱 서버 주소(baseUrl)를 넣습니다. 이때 HMAC 시크릿이 한 번만 표시됩니다.
  3. 아래 계약대로 앱 서버에 /tablerun/pull 또는 /tablerun/push 를 구현합니다.
  4. 콘솔에서 심사 제출 → 승인되면 스토어에 공개됩니다.
  5. 매출과 정산 예정 금액은 콘솔의 앱 상세에서 확인합니다.

필요한 앱을 직접 만들기 어렵다면 맞춤 제작을 요청할 수 있습니다.

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개, 코드 A-Z0-9(120자) 유일, 요금·포함 건수 오름차순. 첫 티어가 설치 기본값.
    • unit 은 실행 방식이 정한다 — 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 (권장): 서버가 있는 앱에 확장을 곁들인다. 확장이 보낸 것을 서버가 처리한다(수집 = collect scope + pull 의 input). 예: 쇼핑몰 상품정보 수집(product-collect).
  • extension 전용 (runtime=extension, 서버 없음): 확장이 앱 토큰으로 붙은 테이블의 행을 직접 읽고 쓴다. scopes 는 tables:read·rows:read·rows:write 만 (수집·실행 조회는 서버가 있어야 뜻이 있다 — 콘솔이 막는다).

remote 앱에 확장을 붙이는 흐름:

  1. 사용자가 앱을 설치하고 앱 설정 화면 앱 토큰에서 토큰(trapp_…)을 발급받아 확장 옵션에 붙여 넣는다 (1회 표시, 설치당 활성 5개, 폐기 즉시 효력).
  2. 확장은 GET /v1/app-token 으로 연결을 확인하고(설치·테이블 이름), 상품 페이지에서 POST /v1/apps/{installId}/collect {url, source} 를 부른 뒤 GET /v1/runs/{runId} 를 폴링한다 (아래 "앱 토큰으로 부르는 API").
  3. 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회(앱 단위), source 4MB. 선언하지 않은 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 — 사용자가 앱 설정에서 티어를 올려야 합니다.

문의

계약·심사·정산에 관한 문의는 문의하기에서 분류를 스토어로 골라 남겨 주세요.