Saeroon Developer Center
1Step 1 Start2Step 2 Connect· My statusDocs

이 문서의 큐

  1. 키가 두 종류입니다
  2. 1. 키 발급
  3. 2. 게시판 시드 — CLI
  4. 3. 게시판 시드 — MCP
  5. 4. 소셜 로그인 — 세션 토큰 받기
  6. 5. 피드 읽기
  7. 6. 피드 쓰기
  8. 같은 내용은 한 장으로 — 중복 방지
  9. 7. 담기(좋아요) 토글
  10. 8. 신고
  11. 9. 차단
  12. 10. 계정 삭제
  13. 응답 필드 이름
  14. 11. 쿠폰 redeem — 회원 등급을 코드로 영구 부여
  15. 지금 되는 것 / 아직인 것
  16. 다음

개요

시작하기

Quickstart

기능 붙이기

FormsMembersBoardsShopCatalogsGroupsHoldings

앱 붙이기

앱 백엔드 붙이기

레퍼런스

속성 계약 (data-saeroon-*)CLI 레퍼런스 (@saeroon/cli)APIMCP

마켓플레이스

Marketplace

문서 / 앱 붙이기

이 문서의 큐
  1. 키가 두 종류입니다
  2. 1. 키 발급
  3. 2. 게시판 시드 — CLI
  4. 3. 게시판 시드 — MCP
  5. 4. 소셜 로그인 — 세션 토큰 받기
  6. 5. 피드 읽기
  7. 6. 피드 쓰기
  8. 같은 내용은 한 장으로 — 중복 방지
  9. 7. 담기(좋아요) 토글
  10. 8. 신고
  11. 9. 차단
  12. 10. 계정 삭제
  13. 응답 필드 이름
  14. 11. 쿠폰 redeem — 회원 등급을 코드로 영구 부여
  15. 지금 되는 것 / 아직인 것
  16. 다음

앱 백엔드 붙이기

정적 사이트가 아니라 네이티브 앱(iOS·Android·Flutter·React Native)을 새로온 백엔드에 붙이는 흐름입니다. 회원·게시판·피드를 앱에서 바로 씁니다.

다른 문서들이 다루는 data-saeroon-* 속성은 브라우저 SDK가 DOM에 붙는 방식이라 앱에서는 쓸 수 없습니다. 앱은 아래의 REST를 직접 호출합니다.

키가 두 종류입니다

앱을 붙일 때 가장 많이 헷갈리는 지점입니다. 용도가 다르고, 서로 대체되지 않습니다.

키쓰는 곳누가 들고 있나
sk_live_...백엔드를 운영할 때 (CLI·MCP·관리 REST)개발자·CI. 앱에 넣지 마세요
세션 토큰 (7일)앱이 런타임에 API를 부를 때앱. 소셜 로그인 응답의 body로 받습니다

sk_live_를 앱에 넣으면 그 키로 사이트 전체를 조작할 수 있게 됩니다. 앱에는 세션 토큰만 둡니다.

1. 키 발급

developers.saeroon.com/keys 에서 새 API Key 생성. 생성 직후 모달에서만 전체 값을 볼 수 있습니다.

키는 사용자당 발급되며, 그 사용자가 소유한 사이트에 닿습니다. 사이트 소유자와 키 소유자가 다르면 404가 납니다 — 401이 아니라 404라서 사이트 ID를 틀린 것처럼 보입니다. 이 점을 먼저 의심하세요.

export SAEROON_API_KEY=sk_live_...

2. 게시판 시드 — CLI

앱이 붙을 게시판을 만들고 boardId를 받습니다. 앱은 slug가 아니라 boardId로 배선합니다.

npx @saeroon/cli board create \
  --site <siteId> --name "피드" --slug feed \
  --type general --write-mode open --json
{ "ok": true, "created": true, "board": { "id": "<boardId>", "slug": "feed", "writeMode": "open" } }

멱등합니다. 같은 slug로 다시 실행하면 "created": false와 함께 기존 board를 돌려줍니다 — 중복 생성이 없으니 스크립트에서 안심하고 반복할 수 있습니다.

--allow-anonymous를 넣지 않으면 익명 게시가 막히고 로그인한 회원만 글을 씁니다(기본값). 읽기는 로그인 없이 열려 있습니다.

3. 게시판 시드 — MCP

에이전트(Claude 등)로 붙일 때는 MCP가 편합니다.

claude mcp add --transport http saeroon https://hosting.saeroon.com/api/mcp/streamable

브라우저가 열리면 사이트를 소유한 계정으로 로그인합니다. 다른 계정으로 로그인하면 연결은 되지만 board 도구가 그 사이트에서 실패합니다.

도구하는 일
saeroon.platform.board.list사이트의 게시판 목록 — site_id
saeroon.platform.board.create게시판 생성(멱등) — site_id, name, slug, type, write_mode

MCP는 sk_live_가 아니라 OAuth 토큰으로 인증합니다. MCP 설정에 sk_live_를 넣으면 401이 납니다.

4. 소셜 로그인 — 세션 토큰 받기

앱이 provider에서 받은 idToken을 넘기면 7일짜리 세션 토큰을 body로 돌려줍니다. 쿠키가 아닙니다.

POST https://api.saeroon.com/api/v1/hosting/public/sites/{siteId}/auth/social/{provider}/native

provider = apple | google

{ "idToken": "<provider id_token>", "authorizationCode": null, "nonce": null }

응답:

{
  "sessionToken": "...",
  "expiresAt": "2026-07-24T00:00:00Z",
  "member": {
    "id": "<uuid>",
    "nickname": "",
    "tier": "free",
    "entitlementActive": false,
    "entitlementSource": null
  }
}

member 는 /me 와 /me/links 도 같은 shape 로 반환합니다.

  • entitlementActive — 등급이 있는지(대략 tier ≠ free).
  • entitlementSource — 등급 부여 출처: "iap"(결제) · "coupon"(컴프 쿠폰) · null(무료/운영자 수동).

결제 후원자와 쿠폰 회원은 둘 다 entitlementActive: true 라 tier·active 만으로는 구분되지 않습니다. 이 필드로 구분하세요. active 인데 entitlementSource 가 없으면(옛 응답) 결제로 간주하는 게 안전합니다 — 그래서 쿠폰 부여는 항상 "coupon" 을 싣습니다(새 기기·재설치 재로그인에서 오표기 방지).

이후 모든 회원 호출에 그대로 붙입니다.

Authorization: Bearer <sessionToken>

member.id는 회원의 내부 UUID입니다. provider의 subject id가 아닙니다. 글의 작성자와 상관되는 키가 이 UUID라, 차단·본인글 판별은 전부 이 값으로 합니다.

이 엔드포인트는 사이트별로 소셜 로그인이 등록돼 있어야 동작합니다. 등록 전에는 404로 닫혀 있습니다 (크래시가 아니라 fail-closed).

소셜 자격증명 등록은 아직 셀프서브가 아닙니다. 보드 시드(§2·§3)는 CLI·MCP로 직접 하지만, Apple·Google 자격증명 등록은 현재 플랫폼 운영자가 대신 넣어 드립니다 — 앱 번들 ID와 provider client ID를 문의로 전달하면 등록해 드립니다. (자격증명 등록 전까지 /native는 404이므로, 앱은 이 응답을 "로그인 미구성" 상태로 다루면 됩니다.)

5. 피드 읽기

GET .../sites/{siteId}/community/boards/{boardId}/posts?sort=new&page=1&pageSize=20

경로에 /community/ 세그먼트가 들어갑니다. 빠뜨리면 404입니다.

파라미터값
sortnew (최신순) · many (많이 담긴 순) · today (오늘, KST 기준)
category정확히 일치하는 문자열만 필터
page / pageSize1부터. size도 별칭으로 받습니다

로그인 없이 읽힙니다. 응답은 { items, total, page, pageSize }입니다.

sort에 없는 값을 주면 400이 아니라 기본 정렬로 조용히 폴백합니다. 오타가 에러로 드러나지 않으니 클라이언트에서 값을 고정하세요.

6. 피드 쓰기

POST .../sites/{siteId}/community/boards/{boardId}/posts
Authorization: Bearer <sessionToken>
{ "title": "...", "content": "...", "category": "인사" }

title과 content는 필수입니다. 빠뜨리면 400이 납니다.

{ "errors": { "Title": ["The Title field is required."] } }

같은 내용은 한 장으로 — 중복 방지

payload 필드를 함께 보내면 그 값의 해시로 게시판 안에서 중복을 걸러냅니다. 이미 같은 내용이 있으면 새로 만들지 않고 기존 글을 그대로 돌려줍니다.

새로 만들었는지 기존 것을 받았는지는 HTTP 상태 코드로만 구분합니다. 응답 body에는 표시가 없습니다.

상태뜻
201새로 만들어짐
200이미 있던 글을 돌려줌

중복 방지가 필요 없는 게시판(예: 짧은 응원처럼 같은 말이 여러 번 올라와도 되는 곳)에서는 payload를 보내지 마세요. 보내면 같은 문구가 한 장으로 합쳐집니다.

7. 담기(좋아요) 토글

POST .../sites/{siteId}/community/posts/{postId}/like

로그인 없이 됩니다. 클라이언트가 만든 익명 ID를 키로 중복 없이 셉니다. 응답은 { "isLiked": true, "likeCount": 12 }입니다.

8. 신고

부적절한 글·댓글을 신고합니다. 로그인 없이도 됩니다(reporterEmail은 선택).

POST .../sites/{siteId}/community/reports
{ "targetType": "Post", "targetId": "<글 또는 댓글 UUID>", "reason": "Inappropriate", "description": null, "reporterEmail": null }
필드값
targetTypePost · Comment
reasonSpam · Harassment · Inappropriate · Copyright · Other
description선택. 자유 서술

신고 누계가 임계(기본 3)에 도달하면 대상이 자동으로 숨겨집니다(App Store UGC 요건). 임계값은 사이트별 설정입니다.

9. 차단

한 회원이 다른 회원을 차단하면, 차단한 사람의 피드·댓글에서 차단당한 사람의 글이 사라집니다(단방향, 뷰어 기준). 로그인 필수입니다.

POST   .../sites/{siteId}/community/blocks           Authorization: Bearer <sessionToken>
DELETE .../sites/{siteId}/community/blocks/{blockedMemberId}
GET    .../sites/{siteId}/community/blocks
{ "blockedMemberId": "<차단 대상 회원 UUID>" }

blockedMemberId는 §4에서 설명한 회원 내부 UUID(authorId로 오는 값)입니다. 자기 자신은 차단할 수 없습니다. POST는 멱등입니다(이미 차단이면 그대로). 차단 해제는 DELETE.

10. 계정 삭제

회원 본인이 자기 계정을 삭제합니다. 로그인 필수. Apple·Google 스토어의 계정삭제 요건입니다.

DELETE .../sites/{siteId}/me    Authorization: Bearer <sessionToken>

호출 즉시 계정이 하드 삭제되고, 그 회원이 쓴 글·댓글의 작성자명은 "[탈퇴한 회원]"으로 익명화됩니다 (글 자체는 남고, 작성자 연결만 끊깁니다). 세션은 무효화되어 기존 토큰은 이후 401이 납니다. 프로필·비밀번호·소셜 연결·차단 기록은 함께 삭제됩니다. 되돌릴 수 없습니다.

응답: { "message": "탈퇴 처리되었습니다." }

응답 필드 이름

읽기 응답의 이름이 직관과 다를 수 있어 미리 적어 둡니다.

받는 값의미
title목록 응답에는 content가 없습니다. 본문은 title로 옵니다
likeCount담긴 수
authorName작성자 표시 이름
authorId작성자 회원 UUID (차단·본인글 판별 키)
metadata글마다 넣어 둔 JSON. 클라이언트가 넣은 값은 그대로 보관·반환됩니다

11. 쿠폰 redeem — 회원 등급을 코드로 영구 부여

로그인한 회원이 쿠폰 코드를 입력하면 회원 등급을 영구 부여받습니다(만료 없음). 세션 토큰 Bearer 필수.

POST https://api.saeroon.com/api/v1/hosting/public/sites/{siteId}/coupons/redeem
Authorization: Bearer <sessionToken>
Content-Type: application/json

{ "code": "MC-XXXX-XXXX" }

성공(200):

{ "tier": "gold", "active": true, "grantedBy": "coupon" }
  • 성공하면 회원의 tier 가 오르고, 이후 /native·/me 의 entitlementSource 가 "coupon" 이 됩니다.
  • 단일사용·회수는 서버가 강제합니다(온라인 관리형 — 오프라인 해시 쿠폰의 재사용 한계 해소).
  • 잘못된/만료된/이미 쓰인 코드 = 비-auth 4xx(사용 불가 쿠폰으로 처리). 401/403·5xx = 진짜 실패(재시도/문의).
  • 이미 결제(iap) 후원자면 409 — 컴프 쿠폰이 결제 정체성을 덮지 않습니다.
쿠폰 발급/회수/사용내역은 개발자센터 도구(CLI coupon * · MCP saeroon.platform.coupon.*)로 하는 운영자 작업입니다 — 앱은 redeem 만 호출합니다. members capability 를 붙인 사이트면 어디나 쓸 수 있는 범용 기능입니다.

지금 되는 것 / 아직인 것

정직하게 적습니다. 아래 표에 없는 기능은 문서에 없는 게 아니라 아직 없습니다.

기능상태
게시판 시드 (CLI·MCP)됩니다 (셀프서브)
소셜 로그인 → 세션 토큰사이트별 소셜 등록 후 됩니다 (등록은 현재 운영자 대행 — §4)
피드 읽기 · 쓰기 · 중복 방지됩니다
담기 토글됩니다
신고 · 차단됩니다 (§8·§9)
계정 삭제 (DELETE /me)됩니다 (§10)
회원 등급 쿠폰 redeem (POST …/coupons/redeem)됩니다 (§11)
회원 shape 의 entitlementSource (iap/coupon)됩니다 (§2 응답)
인앱결제 영수증 검증준비 중 (redeem 과 같은 등급 부여 경로)

다음

  • CLI 커맨드 전체
  • MCP 연결
  • API 키 체계

요약

정적 사이트가 아니라 네이티브 앱(iOS·Android·Flutter·React Native)을 새로온 백엔드에 붙이는 흐름입니다. 회원·게시판·피드를 앱에서 바로 씁니다.

마크다운 원문/docs/app-backend.md