직접 호스팅하는 앱을 DontCode 프로젝트에 연결하세요: 인증, 데이터베이스, 파일 스토리지, 결제, 이메일, 공유 키-값 저장소, 실시간 기능을 프로젝트 API 키 하나로 일반 HTTP를 통해 사용합니다.
퍼블릭 API는 직접 호스팅하는 앱이 DontCode의 플랫폼 서비스(인증, 데이터베이스, 파일 스토리지, 결제, 트랜잭션 이메일, 공유 키-값 저장소, 실시간 pub/sub)를 프로젝트 API 키로 일반 HTTP를 통해 사용할 수 있게 합니다. dontcode SDK와 동일한 와이어 프로토콜을 사용하므로, 앱은 플랫폼 측 변경 없이 해당 SDK와 원시 HTTP 사이를 오갈 수 있습니다.
계약은 안정적입니다: 필드, 상태 코드, 형태는 추가 방식으로만 변경됩니다. 서비스가 내부적으로 어떻게 동작하는지는 계약의 일부가 아닙니다.
DontCode가 처음이신가요? 전체 제품 문서는 여기에 있습니다: dontcode.co/docs
모든 요청은 프로젝트 API 키를 Bearer 토큰으로 전달합니다:
Authorization: Bearer dc_<key>키는 프로젝트 단위로 관리됩니다 (콘솔 → 프로젝트 → API 키). 평문은 생성 시 한 번만 표시되며 해시만 저장됩니다. 폐기는 즉시 적용됩니다.
두 개의 환경 변수로 앱을 게이트웨이에 연결하세요: 키 생성 시 표시되는 패널에서 그대로 복사하면 됩니다:
DONTCODE_API_URL=https://backend.dontcode.co
DONTCODE_API_KEY=dc_…속도 제한은 프로젝트별, 네임스페이스별로 적용됩니다. 실시간 호출이 아무리 많아도 데이터베이스나 인증 호출이 밀리지 않습니다. 한도는 키가 아니라 프로젝트를 따릅니다. 키를 새로 발급하거나 교체해도 한도가 늘어나지 않습니다.
| 한도 | 기본값 |
|---|---|
| /api/v1/realtime | 분당 1200회 |
| /api/v1/cache | 분당 1000회 |
| /api/v1/db 읽기 | 분당 900회 |
| /api/v1/db 쓰기 | 분당 300회 |
| /api/v1/auth | 분당 300회 |
| /api/v1/payments | 분당 120회 |
| /api/v1/storage | 분당 120회 |
| /api/v1/notifications | 분당 60회 |
| /api/v1/db/migrate | 분당 10회 |
/api/v1/db는 읽기와 쓰기로 나누어 한도를 적용합니다. find, findMany, findFirst, findOne, count는 읽기 한도(db/read)를, insert, update, delete는 쓰기 한도(db/write)를 사용합니다. 두 한도는 별개의 카운터이므로, 폴링 루프가 읽기 한도를 모두 써도 쓰기는 계속 처리됩니다. 읽기는 비용이 낮고 다시 시도하면 되지만, 실패한 쓰기는 데이터 손실이기 때문입니다.
모든 응답에 RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, RateLimit-Scope가 포함되므로 차단되기 전에 호출 속도를 조절할 수 있습니다. 하나의 경로가 둘 이상의 한도를 사용할 수 있으므로, 경로로 추측하지 말고 RateLimit-Scope를 읽으세요. 한도를 초과하면 429 { error, rate_limit: true, timeleft, scope }를 반환하며, scope는 소진된 한도의 이름을 나타냅니다. 응답에는 timeleft와 같은 초 단위 값을 담은 Retry-After 헤더도 함께 전달됩니다.
HTTP를 직접 작성하고 싶지 않으신가요? dontcode는 이 페이지의 모든 엔드포인트를 감싸는 얇은 타입드 래퍼입니다. 동일한 와이어 프로토콜과 동일한 형태를 사용하므로 언제든 원시 HTTP로 돌아갈 수 있습니다. fetch가 동작하는 모든 환경(Node 18+, 브라우저)에서 실행되며 런타임 의존성이 없습니다.
pnpm add dontcode클라이언트를 생성하세요. apiKey는 DONTCODE_API_KEY로, 게이트웨이 주소는 DONTCODE_API_URL로 기본 설정됩니다. 위의 두 변수와 동일하므로 값이 설정되어 있으면 dontcode()에 인자가 필요 없습니다. 어느 곳에도 키가 없으면 게이트웨이가 401 Missing API key를 반환합니다.
import { dontcode } from 'dontcode'
const client = dontcode() // reads DONTCODE_API_KEY / DONTCODE_API_URL
// Auth: branch on verification_required / mfa_required, like the raw API
await client.auth.signup({ email, password, role: 'editor' })
const login = await client.auth.login({ email, password })
const { user } = await client.auth.me({ accessToken })
// Database: db.<table> or db('table'); migrations via db.migrate
const rows = await client.db.profiles.find({ where: { ownerId }, limit: 20 })
await client.db.profiles.insert({ name })
await client.db.migrate({ sql })
// Storage: public / private buckets
await client.storage.private.upload('docs/a.pdf', bytes, 'application/pdf')
const { url } = await client.storage.public.getUrl('img/logo.png')모든 메서드는 아래 라우트와 일대일로 매핑됩니다: client.auth.*는 /api/v1/auth/*로, client.db.<table>와 client.db.migrate는 /api/v1/db로, client.storage.public|private는 /api/v1/storage로 연결됩니다. 로그인된 사용자 호출(me, mfa.* 메서드)에는 최종 사용자 토큰을 accessToken으로 전달하면 X-Access-Token에 실립니다.
status, code, 전체 body를 담은 DontCodeError를 던집니다. “한 단계 더” 인증 상태(verification_required, mfa_required)는 오류가 아니라 정상 응답이므로 반환된 값으로 분기하세요.이제 모든 SDK 요청에 타임아웃이 적용되므로(기본 10초, timeoutMs로 변경) 게이트웨이가 느려도 멈추지 않고 빠르게 실패합니다. 매 요청마다 왕복 없이 내비게이션 사이에서 로그인 상태를 유지하려면 아래 세션을 참고하세요.
구조화된 쿼리만 허용됩니다: 원시 SQL은 여기서 절대 받지 않습니다. 본문은 구조화된 쿼리 프로토콜입니다:
{ "operation": "find", "tableName": "maps", "options": { … } }| operation | options | 반환 (data) |
|---|---|---|
| find / findMany | where, select, orderBy, limit, offset | 행 (호출당 최대 1000개) |
| findOne / findFirst | where, select, orderBy | 행 또는 null |
| insert | data | { id } |
| update | where (필수), data | { count } |
| delete | where (필수) | { count } |
| count | where | 숫자 |
where 절: 직접 동등 비교, null(IS NULL), 배열(IN), 연산자 객체(equals, not, gt, gte, lt, lte, in, notIn, contains, startsWith, endsWith, 대소문자 무시는 mode: "insensitive"), 그리고 AND / OR / NOT 조합. 모든 값은 서버에서 파라미터화됩니다.
오류는 { error: string }입니다. 고유/외래 키 충돌은 409를 반환하며, 이는 지원되는 멱등 패턴입니다: insert 후 409가 오면 행이 이미 존재하는 것으로 처리(또는 다시 읽기)하세요. include(관계)는 아직 퍼블릭 API에서 제공되지 않습니다.
{ "sql": "CREATE TABLE IF NOT EXISTS profiles (…);" }외부에서 프로젝트 데이터베이스로 DDL이 들어오는 유일한 통로입니다. 구문은 콘솔이 사용하는 동일한 파이프라인으로 검증·정제됩니다: 단일 스키마 격리, 역할/권한 관리 불가, 확장 불가, 플랫폼 관리 users 테이블 보호. { success, executedStatements, warnings } 또는 400 { success: false, error }를 반환합니다.
DontCode 인증을 안정적인 요청/응답 형태로 프록시합니다. 엔드포인트: signup, login, me, verify-email, forgot-password, reset-password, mfa/challenge, mfa/enroll, mfa/enroll/confirm, mfa/disable.
Authorization: Bearer dc_…)는 *프로젝트*를 식별합니다. 아래 인증 호출을 포함한 모든 요청에 들어갑니다.me, mfa/enroll, mfa/enroll/confirm, mfa/disable)에 보내세요. 세션은 앱이 소유합니다; 토큰은 httpOnly 쿠키에 저장하세요.{ email, password, name?, role?, accept_as_typed? }. 응답은 프로젝트가 이메일 인증을 요구하는지에 따라 달라집니다:
// 인증 ON: 아직 토큰 없음; 사용자가 코드를 입력해야 함
{ "success": true, "userId": "…", "verified": false,
"verification_required": true,
"message": "User created. Please verify your email." }
// 인증 OFF: 계정 즉시 사용 가능; 바로 로그인
{ "success": true, "userId": "…", "verified": true,
"verification_required": false }verification_required가 true이면 앱은 코드를 받아야 합니다. DontCode가 신규 사용자에게 6자리 코드(예: 174829)를 이메일로 보냅니다. 코드 입력 폼을 표시하고 verify-email로 POST하세요; signup만으로는 흐름이 끝나지 않습니다.
DontCode는 계정을 만들기 전에 주소를 확인합니다. 메일을 받을 수 없는 주소나 일회용 메일함은 400 { code: "UndeliverableEmailException" }로 실패합니다. 정상 도메인의 오타로 보이는 주소(gmai.com)는 422 { code: "EmailTypoSuspected" }로 잠시 멈춥니다. 두 경우 모두 did_you_mean이 올 수 있으니 한 번에 고칠 수 있게 보여 주세요. 멈춘 주소를 입력한 그대로 쓰려면 accept_as_typed: true를 붙여 signup을 다시 보내세요. 메일을 받을 수 없는 주소는 이 값으로도 통과하지 않습니다.
// 오타로 보임: 아직 아무것도 만들지 않음
422 { "success": false, "code": "EmailTypoSuspected",
"did_you_mean": "jane@gmail.com" }
// 메일 수신 불가 / 일회용 메일함
400 { "success": false, "code": "UndeliverableEmailException",
"did_you_mean": "jane@naver.com" } // 고칠 주소가 있을 때{ code }: 이메일의 6자리 코드. (email은 받지만 무시됩니다. 코드만으로 사용자를 식별합니다.)
{ "success": true, "message": "Email verified successfully" } // → 이제 login 호출
// 잘못/만료된 코드 → 400 { "success": false, "error": "…" }퍼블릭 API에는 아직 별도의 “재전송”이 없습니다; 미인증 이메일로 signup을 다시 실행하면 코드가 재전송됩니다.
{ email, password }. 두 가지 성공 응답이 있습니다; mfa_required로 분기하세요:
// MFA 없음 → 세션 획득
{ "success": true, "userId": "…", "mfa_offered": false,
"mfa_enabled": false,
"tokens": { "AccessToken": "…", "ExpiresIn": 3600 } }
// MFA 활성 → 아직 로그인 아님; 단기 챌린지 보유
{ "success": true, "userId": "…", "mfa_required": true,
"challenge_token": "…", "challenge_expires_in": 300 }이메일 인증 전 로그인 시도는 403 { code: "EmailNotVerified" }로 실패합니다; 사용자를 verify-email 단계로 되돌리세요.
login이 mfa_required를 반환했다면, 챌린지와 사용자의 인증 앱 코드를 실제 토큰으로 교환하세요: { challenge_token, code } (또는 복구 코드를 사용하려면 { challenge_token, recovery_code }).
{ "success": true, "tokens": { "AccessToken": "…", "ExpiresIn": 3600 } }
// 만료 → 401 { "code": "ChallengeExpired" } → login 다시 시작MFA는 사용자별 선택 사항입니다. 프로젝트의 MFA-제공 설정은 등록을 가능하게 할 뿐, 누구에게도 강제하지 않습니다. 사용자는 mfa/enroll/confirm을 완료한 후에만 mfa_required 로그인 분기를 보게 됩니다. 소유자가 프로젝트에 MFA를 활성화하지 않았다면 mfa/enroll은 403 { code: "MfaNotOffered" }를 반환합니다.
{ success, secret, otpauth_url }. otpauth_url을 QR 코드로 렌더링(또는 secret 표시)하여 사용자의 인증 앱에 등록하게 하세요. 등록은 보류 상태입니다; mfa_enabled는 아직 바뀌지 않습니다.{ code }: 인증 앱의 첫 코드. 성공 시 → { success, recovery_codes: [...] }. 복구 코드를 한 번만 보여주고 저장하도록 안내하세요; 다시 반환되지 않습니다.{ code } 또는 { recovery_code }: MFA를 끄기 전에 두 번째 인증 요소 소유를 증명합니다 → { success, message }.본문 없음. 사용자 토큰을 X-Access-Token으로 보내세요 → { user: { id, email, role, claims } }, 토큰이 없거나 만료되면 401 { user: null }.
앱은 올바른 인증 플로우에 필요한 2단계 회원가입 및 로그인 화면을 직접 만드는 것입니다. 이 화면들이 따르는 규칙: 한 번의 제출이 “완료” 상태 또는 “한 단계 더” 상태를 반환할 수 있습니다. 응답에 따라 분기하고, 한 번의 왕복으로 끝난다고 가정하지 마세요. 이를 잘못 처리하는 것이 BYOC의 가장 흔한 버그입니다: 회원가입 폼이 코드 단계를 빠뜨리고, 로그인 폼이 MFA 단계를 빠뜨립니다.
| 이 호출 후 | 응답 신호 | UI가 해야 할 일 |
|---|---|---|
| signup | verification_required: true | 6자리 코드 폼으로 전환 → verify-email, 그 다음 login |
| signup | verified: true | 계정 사용 가능 → 사용자를 login으로 |
| login | tokens 존재 | 세션 저장; 완료 |
| login | mfa_required: true (+ challenge_token) | 인증 앱 코드 폼으로 전환 → mfa/challenge |
| login | mfa_offered: true, mfa_enabled: false | 선택적으로 “2단계 인증 켜기” 유도 → 등록 흐름 |
| me | 401 / user: null | 로그아웃 상태로 처리 |
MFA를 필수로 만들기: 서버는 MFA를 강제하지 않습니다; login은 이미 등록한 사용자에게만 mfa_required를 반환합니다. 모두에게 요구하려면 로그인 후 앱에서 게이트하세요: mfa_offered: true이고 mfa_enabled: false이면 진행 전에 사용자를 등록 흐름으로 보내세요.
이들은 새로운 엔드포인트가 아니라 SDK 편의 기능이며, 와이어 계약은 그대로입니다. 세션은 여전히 앱이 저장한 액세스 토큰일 뿐입니다. 이 기능은 한 가지 함정을 해결합니다: 매 내비게이션마다 me를 호출해 인증을 확인하면 핫 패스에서 네트워크 왕복이 발생하고, 게이트웨이가 느리면 그 멈춘 요청이 “로그아웃됨”으로 읽혀 사용자를 로그인으로 되돌립니다. 이 헬퍼로 라우트는 로컬에서 게이트하고, 꼭 필요할 때만 검증하세요. 프레임워크에 구애받지 않습니다: 토큰이나 쿠키 헤더를 받아 일반 데이터를 반환하므로 Next 미들웨어, SvelteKit hooks, Express, 워커 어디든 어댑터 없이 들어갑니다.
serializeSessionCookie(token, options?)는 Set-Cookie 값을 반환하고, clearSessionCookie()는 로그아웃용 값을, readSessionToken(cookieHeader)는 토큰을 다시 읽어옵니다. 문자열을 적용하는 것은 프레임워크의 몫입니다. 기본값은 DontCode 자체 앱의 저장 방식과 동일합니다: httpOnly(클라이언트 JavaScript가 토큰을 읽지 못함), Secure, SameSite=Lax, 경로 /, 이름 dc_access_token, 7일 수명. 교차 사이트 구성에서는 sameSite: "none"이 필요하며 이는 Secure를 강제합니다.
import { serializeSessionCookie, clearSessionCookie } from 'dontcode'
// after a successful login (tokens.ExpiresIn keeps cookie + token in lockstep)
headers.append('Set-Cookie', serializeSessionCookie(login.tokens.AccessToken, {
maxAge: login.tokens.ExpiresIn,
}))
// on logout
headers.append('Set-Cookie', clearSessionCookie())client.auth.getSession({ accessToken, mode })(또는 쿠키를 대신 읽어주는 sessionFromCookies(cookieHeader, { mode }))는 토큰을 { status, user, verified, expiresAt }로 해석합니다. status는 active, expired, anonymous, unavailable 중 하나입니다.
// route guard: instant, no network, runs on every navigation
const session = await client.auth.sessionFromCookies(cookieHeader)
if (session.status !== 'active') return redirect('/login')
// before a sensitive action: confirm against the gateway
const checked = await client.auth.getSession({ accessToken, mode: 'verified' })
if (checked.status !== 'active') return redirect('/login')exp 이전에는 감지하지 못합니다. 토큰은 게이트웨이가 공유하지 않는 비밀 키로 서명되므로 로컬 코드는 토큰의 진위도 증명할 수 없습니다; 오직 게이트웨이만 가능합니다.me를 한 번 호출하고 ttlMs(기본 60초) 동안 캐시하며 verifyTimeoutMs(기본 5초) 후 타임아웃됩니다. 타임아웃이나 장애 시에는 status: "unavailable"와 함께 optimistic으로 디코딩한 사용자를 반환하므로, 모두를 잠그는 대신 페일 오픈할지 직접 결정할 수 있습니다. 실제 401은 anonymous로 보고됩니다: 사용자가 정말로 로그아웃된 것입니다.| 모드 | 네트워크 | 폐기 감지 | 용도 |
|---|---|---|---|
| optimistic (기본값) | 없음 | 아니오, exp까지 | 페이지 로드 게이트, UI 상태 |
| verified | me, 캐시 + 타임아웃 | 예, ttlMs 이내 | 민감한 작업, 권한 확인 |
{ operation, bucket: "public" | "private", … }로 POST:
| operation | params | 반환 |
|---|---|---|
| list | prefix? | { objects, folders, prefix, truncated, continuationToken } |
| remove | paths[] | { deleted } |
| move | from, to | { object } |
| createFolder | path | { created } |
| download | path | { body (base64), contentType, size } (≤ 8 MB) |
| getUrl | path (public 버킷만) | { url } |
| getTemporaryUrl | path, expiresIn? | { url, expiresIn } |
| presignUpload | path, contentType? | { url, key, expiresIn } |
multipart/form-data(file, bucket, path, contentType?)로 PUT하면 직접 업로드되며(≤ 100 MB) { object }를 반환합니다. 더 큰 파일: presignUpload 후 반환된 URL로 파일을 PUT하세요. 모든 경로는 자동으로 프로젝트 범위로 한정됩니다.
일회성 결제를 받고 구독을 운영하며, 요금제·기능 카탈로그와 이용 권한 확인을 제공합니다. 이것은 서버 컨트롤 플레인입니다: 서버가 완료된 결제를 검증하고, 구독 수명 주기를 관리하며, 누가 무엇에 대한 권한을 갖는지 조회합니다. 고객이 보는 결제 팝업은 결제 제공자를 대상으로 브라우저에서 실행되며, 게이트웨이는 그 결과를 실제로 반영하기 위해 서버가 호출하는 대상입니다. 금액은 요금제 통화(기본 KRW)의 정수이며, 모든 것은 자동으로 프로젝트 범위로 한정됩니다.
userId를 명시적으로 전달합니다. 작업(operation)은 본문 필드가 아니라 경로의 일부이며, SDK의 client.payments.*와 일대일로 매핑됩니다.| method + path | body | 반환 |
|---|---|---|
| POST /verify | { paymentId, expectedAmount, currency, description?, userId? } | { receipt } |
| POST /refund | { paymentId, amount?, reason } | { refundedAmount, status, already_refunded } |
verify는 고객이 방금 완료한 결제를 기록합니다; 무언가를 부여하기 전에 호출하세요. refund에서 amount를 생략하면 전액 환불이며, 환불은 멱등적입니다.
| method + path | body | 반환 |
|---|---|---|
| POST /subscribe | { plan, userId, method, billingKey } | { subscription } |
| POST /subscribe-reserve | { plan, userId, method } | { subscription_id, storeId, channelKey } |
| POST /subscribe-confirm | { subscription_id, billing_key } | { subscription } |
| POST /subscribe-abort | { subscription_id } | {} |
| POST /cancel-subscription | { subscription, atPeriodEnd } | { subscription } |
| POST /update-subscription-status | { subscription, status } | { subscription } |
| POST /get-subscription | { userId } 또는 { subscriptionId } | { subscription } 또는 null |
| POST /list-active-subscriptions | { userId } | { subscriptions } |
시작하는 두 가지 방법: 이미 빌링 키를 보유했을 때는 단일 호출 subscribe, 아니면 분할 흐름 subscribe-reserve 후 반환된 구성으로 브라우저에서 제공자 팝업을 열고 subscribe-confirm(팝업이 취소되면 subscribe-abort)을 호출합니다. cancel-subscription은 atPeriodEnd: false가 아니면 기본적으로 소프트 취소(기간 종료까지 접근 유지)입니다.
| method + path | body / query | 반환 |
|---|---|---|
| POST /has-feature | { userId, featureKey } | { ok } |
| POST /list-user-features | { userId } | { features } |
| GET /plans | ?includeInactive=true | { plans } |
| POST /plans | { plans } | { plans } |
| PATCH /plans | { planId, active } | {} |
| DELETE /plans | ?planId= | {} |
| GET /features | { features } | |
| POST /features | { features } | { features } |
| DELETE /features | ?featureKey= | {} |
| GET /plan-features | ?planId= | { planFeatures } |
| PUT /plan-features | { planId, features } | {} |
요금제는 가격이 매겨진 티어, 기능은 이름이 붙은 기능이며, plan-features는 어떤 요금제가 어떤 기능을 부여하는지 매핑합니다(요금제별 한도는 metadata에 들어갑니다). has-feature는 유료 기능의 게이트입니다; 반드시 확실해야 하는 것은 세션과 동일한 규칙으로 서버에서 다시 확인하세요.
| method + path | body | 반환 |
|---|---|---|
| POST /admin/list-subscriptions | { status?, planId?, userId?, limit?, offset? } | { subscriptions, total } |
| POST /admin/grant-subscription | { userId, planId, periodStart?, periodEnd? } | { subscription } |
| POST /charge-subscription | { subscription, plan } | { receipt } |
grant-subscription은 결제 없이 구독을 무상 부여합니다(자동 갱신 없음). charge-subscription은 주기 외 결제를 트리거합니다; 갱신은 자동으로 실행되므로 거의 필요하지 않습니다. 이 계약을 벗어난 경로는 404입니다.
결제 호출이 실패하면 status, 안정적인 code, 전체 body를 담은 DontCodeError가 발생합니다. 메시지는 구매자가 읽을 수 있도록 작성되어 있으므로, 모든 실패를 하나의 일반 메시지로 뭉뚱그리지 말고 그대로 표시하세요. 402는 버그가 아닙니다: 보통 설정이 완료되지 않았거나 구매자가 다시 시도해야 함을 의미합니다.
| code | 상태 | 의미 | 해결 방법 |
|---|---|---|---|
| PAYMENTS_DISABLED | 402 | 이 프로젝트의 결제 처리가 아직 켜지지 않았습니다. 아래 항목보다 먼저 확인됩니다. | 코드에서 바꿀 것은 없습니다: 프로젝트에 결제가 활성화되면 청구가 동작합니다. 계속되면 hello@dontcode.co로 문의하세요. |
| BANK_ACCOUNT_REQUIRED | 402 | 앱에 정산 은행 계좌가 없어 아직 결제를 받을 수 없습니다. | 앱 소유자: 프로젝트 결제 탭에서 정산 계좌를 등록한 뒤 인증이 완료될 때까지 기다리세요. |
| BANK_ACCOUNT_PENDING_VERIFICATION | 402 | 정산 계좌가 아직 인증 중입니다. | 인증이 끝날 때까지 기다리세요; 결제는 자동으로 재개됩니다. |
| BANK_ACCOUNT_GRACE_EXPIRED | 402 | 정산 계좌가 유예 기간을 넘겨 미인증 상태로 남아 있습니다. | 앱 소유자: 프로젝트 결제 탭에서 계좌를 다시 인증하세요. |
| BANK_ACCOUNT_SUSPENDED | 402 | 정산 계좌가 정지되었습니다. | 지원팀에 문의하세요. |
| CHARGE_FAILED | 402 | 결제가 거절되었습니다. | 구매자: 다시 시도하거나 다른 결제 수단을 사용하세요. |
| PAYMENT_VERIFICATION_FAILED | 402 | 완료된 결제를 기록된 금액과 대조해 검증할 수 없습니다. | 금액과 통화가 요금제와 일치하는지 확인한 뒤 다시 시도하세요. |
| SUBSCRIPTION_NOT_FOUND | 404 | 해당 id 또는 사용자에 대한 구독이 없거나, 저장된 결제 수단이 없습니다. | 확정 또는 청구 전에 구독을 다시 예약(reserve)하세요. |
| BILLING_KEY_MISSING | 400 | 이 작업에는 저장된 결제 수단이 필요하지만 존재하지 않습니다. | reserve 후 confirm 흐름을 실행해 결제 수단을 저장하세요. |
| RATE_LIMITED | 429 | 이 프로젝트의 결제 요청이 너무 많습니다. | `timeleft` 초만큼 기다린 뒤 다시 시도하세요. |
BANK_ACCOUNT_REQUIRED를 반환합니다. 새로 등록한 계좌는 인증 유예 기간 동안 결제를 받을 수 있고, 유예 기간이 끝날 때까지 인증되지 않으면 청구가 BANK_ACCOUNT_PENDING_VERIFICATION을 반환합니다. 이는 실패가 아니라 예상된 동작입니다: 구매자에게 막다른 길을 보여주지 말고, 소유자가 설정을 마칠 수 있도록 메시지를 표시하세요.짧게 유지되는 공유 상태를 위한 프로젝트 범위 키-값 저장소입니다. 앱이 어디에서 어떻게 호스팅되든, 모든 인스턴스가 같은 곳을 읽고 씁니다. 서버리스와 다중 인스턴스 환경은 프로세스 메모리를 공유할 수 없으므로, 인스턴스 간 상태는 여기에 둡니다: 요청 제한 카운터, 일회용 코드와 로그인 논스, 웹훅 중복 방지 가드와 단일 실행 잠금, 비회원 장바구니와 다단계 마법사 세션, 그리고 정말 비용이 큰 작업(대규모 집계, 유료 외부 API, AI 호출)의 결과 캐시가 그 예입니다. 값은 앱을 다시 배포해도 유지되며, 같은 키를 쓰는 웹 앱과 워커, 크론이 함께 공유합니다.
| 메서드 + 경로 | 본문 / 쿼리 | 반환 |
|---|---|---|
| GET /kv/{key} | { value } 또는 404 | |
| PUT /kv/{key} | 본문 = 원시 JSON 값; ?ttl=(초), ?nx=true | { ok, set } |
| DELETE /kv/{key} | { ok, deleted } | |
| POST /expire/{key} | { ttl } (초, null이면 해제) | { ok, applied } |
| POST /hset/{key} | { fields: { … } } | { ok, written } |
| GET /hgetall/{key} | { value } 또는 404 | |
| POST /sadd/{key} | { members: string[] } | { ok, added } |
| GET /smembers/{key} | { value } 또는 404 | |
| POST /srem/{key} | { members: string[] } | { ok, removed } |
ttl은 초 단위입니다. PUT의 ?nx=true는 키가 없을 때만 저장하고 이미 있으면 { set: false }를 반환합니다; 이 플래그 하나가 잠금, 단 한 번만 실행되는 작업, 웹훅 중복 제거의 기본 재료입니다. 없거나 만료된 키를 GET하면 404이며, SDK는 이를 null(세트 읽기는 [])로 변환합니다. 문자열, 해시, 세트 키 공간은 서로 구분됩니다. 키는 자동으로 프로젝트 범위로 격리되므로 접두사를 직접 붙이지 마세요.
/api/v1/db에 두세요. 가벼운 읽기에도 쓰지 마세요; 캐시 왕복 비용은 데이터베이스 쿼리 한 번과 비슷하므로, 이미 빠른 쿼리가 아니라 비용이 큰 작업을 캐시하세요.채팅, 접속 상태(presence), 실시간 업데이트 같은 라이브 기능을 위한 WebSocket 기반 실시간 pub/sub입니다. 게이트웨이는 컨트롤 플레인입니다: 서버가 단기 연결 토큰을 발급하고, 메시지를 발행하고, presence를 조회합니다. 브라우저는 발급된 토큰으로 WebSocket에 연결하며 프로젝트 API 키를 절대 갖지 않습니다. 채널은 자동으로 프로젝트 범위로 격리됩니다.
| 메서드 + 경로 | 본문 | 반환 |
|---|---|---|
| POST /token | { channels: string[], identity?, ttl? } | { token, url } |
| POST /publish | { channel, payload } | { delivered } |
| GET /channels/{channel}/presence | { presence: [{ id, identity? }] } |
서버가 최종 사용자를 인증한 뒤 그 사용자가 쓸 수 있는 채널로 토큰을 발급하고, { token, url }을 브라우저에 전달하면 브라우저가 ${url}?token=${token}으로 연결합니다. 전달은 기록이나 재생이 없는 일회성이므로, 보존해야 하는 내용은 /api/v1/db에 저장하세요.
사용자에게 트랜잭션 이메일을 보냅니다. 수신자와 발신자 정보는 자동으로 프로젝트 범위로 지정됩니다. 이 엔드포인트에는 프로젝트 API 키만 접근할 수 있고 최종 사용자 토큰은 쓸 수 없습니다: 누구에게 알릴지는 서버가 정합니다. SDK에서는 client.notifications.email.send에 대응합니다.
| 메서드 + 경로 | 본문 | 반환값 |
|---|---|---|
| POST /email | { to, subject, markdownText } | { success, messageId? } 또는 { success: false, error } |
to는 수신자 이메일 하나 또는 이메일 배열입니다.markdownText가 본문 전체이며 GitHub 방식 Markdown으로 씁니다. html, body, text 필드나 템플릿은 없습니다: 보낸 Markdown이 곧 이메일입니다. 내용은 서버에서 정리됩니다.success를 확인하세요.현재는 email만 있습니다. 경로의 마지막 부분이 채널이므로 이후 채널은 이 계약을 바꾸지 않고 추가되며, 그 밖의 채널은 404입니다.
DONTCODE_API_URL과 DONTCODE_API_KEY로 앱을 설정하세요(키 표시 패널의 환경 변수 블록을 복사)./api/v1/db/migrate로 앱 스키마를 적용하세요./api/v1/auth/*로 가입/로그인, /api/v1/db로 읽기/쓰기, /api/v1/storage로 업로드합니다. Postgres 연결 문자열도, 인증 제공자도, 스토리지 자격 증명도 필요 없습니다.