Contract status
This is a proposed rehearsal contract. Required fields in OpenAPI describe the proposed wire format; they are not evidence that DCinside has agreed to every field. Existing implementation restrictions are identified below so that they can be resolved before rehearsal.Base URL and authentication
The planned rehearsal host ishttps://dcinside-rtb-api.stg.kr.ask.surf. Cut over only after deployment and reachability verification; this documentation does not declare it available. Surf provisions a long-lived API key. The initial sender allowlist is 202.136.121.12/32; additional sender addresses can be added.
The current API accepts the key as the whole header value:
403 scope check is active. /v1/health currently permits unauthenticated access.
Formats
Cycle interval
The latest rehearsal requirement fixes five-minute cycles, matching 288 daily cycles. The implementation also supportsRTB_CYCLE_INTERVAL_MINUTES=2, 3, 4 or 5, with boundaries anchored at KST midnight. The default is five minutes; the rehearsal deployment should keep that setting.
The agreed wire shape uses the KST cycle time as cycle_id, for example 20260929T0205. Keep that ID stable on retries. New cycles may be up to 60 seconds ahead of server time, inclusive, to tolerate clock differences.
Retries and recovery
For cycles, the same ID and same JSON body returns the original completed result; the same ID with a different body returns409 cycle_conflict. JSON object key order is ignored; unknown fields still participate in request identity. GET /v1/scores/{cycle_id} retrieves that result without scoring again. A new cycle may produce new scores for the same eligible pending posts. If recovery finds no completed result, include candidates and removals again in the next cycle: identity is gall_id plus post_no, duplicate deliveries are stored/count once, and removed posts cannot be revived by a retry.
Skipping a late score does not mean discarding undelivered candidate data or removals. Retain unacknowledged data until receipt is established by replay, retrieval or reconciliation. Avoid changing a possibly accepted request under the same ID. Cycle responses, queue state, complete observation histories, removals, weights and image bytes persist in SQLite on a durable volume. Preparation commits state changes with the cycle’s frozen raw scores and exact deduplication inputs. Raw-scoring failure before that commit rolls back those changes, while the first valid receipt and request identity remain durable. After preparation, interruption leaves the prepared cycle recoverable: an unchanged retry resumes it without repeating ingestion or scoring. The final response is persisted once after the bounded wait. Restart and backup recovery still need verification in the deployed environment.
The client requires a total 60-second decision window from sending, including at most one retry. The transport helper shares this budget across attempts, backoff, body reads and parsing, and rejects late results. The worker still owns the hard decision cutoff: platform DNS and some synchronous urllib header/TLS operations cannot be forcibly cancelled by this helper. Late GET/replay remains reconciliation only. See worker integration for the explicit limits. There is no automatic 202/polling switch.
After preparation, the API waits without holding a database writer lock for the exact cycle’s needed background work to settle, or until the remaining wait budget expires. Finding a usable candidate is not an early-exit threshold. Validated facts completed during this wait can enter the same cycle’s final report. A valid full-evidence unknown remains unchecked without repeated calls until its evidence or model/prompt version changes; unavailable evidence can also leave unchecked rows when the wait ends. Transient retry cooldown does not mean settlement. The default 25-second server budget covers queue work, scoring, waiting and finalization, with a finalization reserve. Background checks can continue afterwards, but never rewrite a completed response. This setting does not prove the client’s end-to-end SLA or guarantee conclusive checks for the whole queue. Newly finalized rankings exclude confirmed duplicates only; unchecked candidates remain eligible without being certified unique. Historical completed responses keep their original ranking policy.
The operator-independent pool is a sender policy, separate from retry handling. Saved/hidden labels stay client-side; keep sending pending engagement after first scoring until deletion or the client’s 24-hour age filter triggers a removal. Keep original extracted_at, client-side decision times and delivered removal decided_at; sending later does not reset the corresponding clock. A removal timestamp needs an explicit offset and cannot be after cycle_time; it need not be recent. Saved/hidden removals remain accepted for compatibility, and saved still requires tier. Any received removal takes effect before scoring and stays effective after restart. Resending does not revive a removed post; retrospective backfill requires separate agreement.
Current service protections
These are engineering protections, not client quotas. In particular, 5,000 is not a limit on candidates or the persistent pending pool. Validate each array and decompressed body against the full queue, including posts retained after operator decisions. If a complete cycle exceeds a protection, change the limit or agree a batch protocol; never silently truncate or sample the queue. Different bodies cannot be split under one cycle ID. Pagination is not currently implemented.
DCinside handles the 24-hour age filter and sends aged-out removals; the server does not expire pending posts automatically. About 2,200 new posts/day is an estimate, not an API limit. The projected 2,220-post peak pending pool also is not a quota or capacity guarantee: representative histories, payload sizes, scoring and dedup work need deadline measurements. The 60-second client budget starts when sending; pre-send engagement collection is separate.