> ## Documentation Index
> Fetch the complete documentation index at: https://surf-dcinside-api.kr.ask.surf/llms.txt
> Use this file to discover all available pages before exploring further.

# Conventions

> Base URL, authentication, formats, idempotency and limits shared by every call

<div style={{ maxWidth: 760 }}>
  <h2 style={{ fontSize: "22px", marginBottom: 8 }}>Base URL and authentication</h2>

  Every call is HTTPS with a JSON body (TLS only). The base URL is issued with the API key, and each environment has its own host and its own key, first rehearsal and then production.

  Every call carries the key in the `Authorization` header, as the whole header value:

  ```http theme={null}
  Authorization: <api-key>
  ```

  The key is long-lived, in place of the 7-day link used for the file deliveries, and it is scoped to these calls. That is the API's counterpart of the delivery-folder credential DCinside asked for.

  <Note>
    Two keys are valid during a rotation, which happens on a fixed schedule. Keys are handed over outside Slack. A key can be limited to some calls, and a call outside its scope returns `403`. If DCinside has fixed egress addresses, an IP allowlist can be added.
  </Note>

  ***

  <h2 style={{ fontSize: "22px", marginBottom: 8 }}>Formats</h2>

  | Topic | Rule |
  | - | - |
  | Encoding | Request and response bodies are JSON in UTF-8. Bodies of `POST /v1/cycles` (and `POST /v1/outcomes`, not in MVP scope) are gzip-compressed and sent with `Content-Encoding: gzip`. A busy cycle is about 0.6 MB of JSON. |
  | Times | RFC 3339 in Korean local time with an explicit `+09:00` offset, for example `2026-09-29T02:05:00+09:00`. `cycle_time` sits on the five-minute grid, and observation times are to the minute. The model's hour-of-day and day-of-week inputs are read from `cycle_time` in KST, which is why the offset is required. |
  | Counters | Non-negative integers. `null` means unknown and is never replaced by `0`, because the model treats a missing value differently from a zero. |
  | Post key | A post is keyed by `gall_id` (string) and `post_no` (integer). Post numbers are unique only within a gallery. |
  | Field names | The column names of the delivered v1.2 and v1.3 files wherever a column exists, so the export code DCinside wrote for those files can be reused. Each field description names its delivered column. |
  | Extra fields | Fields we do not know are ignored, and the response names them in `warnings`. |

  ***

  <h2 style={{ fontSize: "22px", marginBottom: 8 }}>Idempotency</h2>

  Calls that change state are safe to repeat.

  | Call | Key | Same key, same body | Same key, different body |
  | - | - | - | - |
  | `POST /v1/cycles` | `cycle_id` | Returns the stored response. Nothing is scored twice. | `409 cycle_conflict`. The first body stays. |
  | `PUT /v1/weights` (not in MVP scope) | `version` | `200`, nothing changes. | `409 version_conflict`. |
  | `POST /v1/outcomes` (not in MVP scope) | `placement_id` | Updates the placement with any new readings. | Updates it. |
  | `PUT /v1/images/...` | image position | `200`, nothing changes. | |

  ***

  <h2 style={{ fontSize: "22px", marginBottom: 8 }}>Cycle interval</h2>

  Five minutes is the proposed default. DCinside's interval is a setting that starts at 2–5 minutes. `cycle_time` is on the five-minute grid and the model's growth windows are built on a five-minute series, so this proposal starts at five. A shorter interval needs a change to the grid rule and to the window features.

  ***

  <h2 style={{ fontSize: "22px", marginBottom: 8 }}>Limits and timeouts</h2>

  <Note>
    These limits are proposed. They come from the queue snapshots of 14–21 September: 649 posts were pending on average (p95 1,203, max 1,381), and 7.8 new posts arrived per five-minute cycle (p95 17, max 36).
  </Note>

  | Limit | Value |
  | - | - |
  | Body of `POST /v1/cycles` | 16 MB after decompression |
  | `candidates` in one cycle | 200 |
  | Rows in any other array of a cycle | 5,000 |
  | Records in one `POST /v1/outcomes` (not in MVP scope) | 1,000 |
  | Post age | 24 hours: DCinside filters before sending, and a pending candidate that crosses it gets a removal signal. DCinside's suggestion, adjustable |
  | Candidates per day | About 2,200: the full admin extraction queue that operators review |
  | Request timeout for `POST /v1/cycles` | Proposed default of 60 seconds, to be settled on the technical exchange and set from the first rehearsal day |

  Scoring runs inside the request, so the timeout has to cover embedding a burst of new posts. If synchronous scoring proves too slow, the call switches to a `202` reply and the result is polled from `GET /v1/scores/{cycle_id}`.
</div>
