> ## 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.

# Upload an Image

> Proposed image transfer for new rehearsal candidates

DCinside sends each candidate's images once when it enters the queue. Surf proposes a separate upload endpoint; HTTPS versus an S3 folder and the exact layout remain open technical decisions.

The existing proposal follows the delivered v1.3 layout, `<gall_id>/<post_no>/<NN>.jpg`. `NN` is the original body's one-based image position, zero-padded to at least two digits. Gaps are permitted when a source image cannot be fetched. Preserve original positions rather than renumbering.

The delivered image convention includes all images, resized to a maximum 1,024-pixel long edge without upscaling. The request body for this endpoint is JPEG. The current server has a 5 MiB per-image engineering limit; verify it against rehearsal data rather than treating it as a client quota.

`images_in_post` and optional `image_positions` describe expected uploads. Confirm these fields and missing-image handling with the client. **Whether scoring waits for image features is not settled by using a separate upload route.** Verify image readiness and fallback behavior with the actual Model 0924 pipeline before rehearsal.

The optional `X-Content-SHA256` header checks the uploaded bytes. Repeating the same image path with identical bytes is safe in the current implementation. Different bytes at an existing image key return `409`; the stored image is preserved. Verify image retention and replay across restarts before rehearsal. The current route accepts only 1–3 digits for `NN` and stores a three-digit name; confirm this path convention against the client image layout rather than assuming it matches every source filename.


## OpenAPI

````yaml openapi.json PUT /v1/images/{gall_id}/{post_no}/{NN}
openapi: 3.0.3
info:
  title: DCinside RTB API
  description: >-
    Proposed contract for DCinside's scoring-only dress rehearsal. Real queue
    candidates go to Surf; DCinside logs would-be picks while operators continue
    publishing. The model does not publish during rehearsal. The intended model
    is the delivered Model 0924 with down-vote weight 0; the deployed
    transport-test adapter is not that artifact. Five-minute cycles, inline
    responses, pending re-scoring, payload details and authentication are Surf
    proposals for confirmation. The response deadline is not yet agreed.
    DCinside's draft first-score target is at least 99% within two cycles of
    queue entry, with no separate scoring wait. This specification describes
    target rehearsal behavior, not deployment readiness. See the rehearsal
    documentation for implementation gaps and acceptance criteria.
  version: 1.1.0-draft
servers:
  - url: https://{host}
    description: Issued by Surf together with the API key.
    variables:
      host:
        default: rtb.example.com
        description: >-
          Host name issued by Surf for the environment (rehearsal or
          production). The default is a placeholder.
security:
  - ApiKey: []
tags:
  - name: Scoring
    description: >-
      Proposed cycle scoring and retrieval for rehearsal; cadence, refresh
      policy and deadline need confirmation.
  - name: Images
    description: >-
      Proposed one-time image transfer; layout and scoring readiness behavior
      need confirmation.
  - name: Outcomes
    description: >-
      Not in scope for trial. Reference for later outcomes-endpoint integration.
      Daily operator outcomes may be shared separately by agreement.
  - name: Weights
    description: >-
      Not in scope for trial. Reference for later client-facing
      weight-management integration. Surf configures the initial trial model and
      weights.
  - name: Health
    description: >-
      Service information; not proof of model readiness, durable state or alert
      delivery.
paths:
  /v1/images/{gall_id}/{post_no}/{NN}:
    put:
      tags:
        - Images
      summary: Upload an Image
      description: >-
        Proposed separate upload for each candidate image, using the original
        body position. HTTPS versus S3 and transfer details remain open.
        Repeating identical bytes is supported. A separate route does not
        establish that scoring can ignore missing image features; readiness and
        fallback behavior must be verified with the delivered model. Current
        image size protection is 5 MiB, not an agreed business quota.
      operationId: putImage
      parameters:
        - name: gall_id
          in: path
          required: true
          schema:
            type: string
            minLength: 1
            example: example_gallery
          description: Gallery id.
        - name: post_no
          in: path
          required: true
          schema:
            type: integer
            minimum: 1
            example: 31900001
          description: Post number.
        - name: NN
          in: path
          required: true
          schema:
            type: string
            pattern: ^\d{2,}$
            example: '01'
          description: >-
            The image's position in the original body: one-based, zero-padded to
            at least two digits, gaps allowed. The v1.3 layout
            `<gall_id>/<post_no>/<NN>.jpg`.
        - name: X-Content-SHA256
          in: header
          required: false
          schema:
            type: string
            pattern: ^[0-9a-f]{64}$
          description: >-
            Hex SHA-256 of the body. When present we verify it, and a mismatch
            is rejected with 400.
      requestBody:
        required: true
        content:
          image/jpeg:
            schema:
              type: string
              format: binary
              description: >-
                The JPEG, 1024 px on the long edge. Smaller originals are not
                upscaled.
      responses:
        '200':
          description: Already stored with the same bytes.
        '201':
          description: Stored.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: >-
            An image already exists at this key with different bytes. The
            existing image is preserved.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: conflict
                  message: image already exists with different bytes
        '413':
          $ref: '#/components/responses/TooLarge'
        '415':
          $ref: '#/components/responses/UnsupportedMediaType'
        '500':
          $ref: '#/components/responses/ServerError'
components:
  responses:
    BadRequest:
      description: >-
        Malformed JSON or a request-schema failure. Semantic checks may return
        only the first error; no exhaustive-error guarantee.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: bad_request
              message: 2 fields failed validation
              fields:
                - path: candidates[0].created_at
                  problem: timestamp must include an explicit offset
                - path: engagement[3].views
                  problem: must be a non-negative integer or null
    Unauthorized:
      description: The API key is missing or unknown.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
              message: API key missing or unknown
    TooLarge:
      description: >-
        Current implementation size protection: 16 MiB JSON after decompression
        or 5 MiB per image. These are engineering protections, not agreed client
        quotas.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: too_large
              message: JSON body exceeds 16 MiB after decompression
    UnsupportedMediaType:
      description: The body is not a JPEG.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unsupported_media_type
              message: expected image/jpeg
    ServerError:
      description: >-
        Server failure. Skip late or unusable scores for the decision cycle,
        preserve unacknowledged data and reconcile delivery. Alert delivery must
        be verified separately.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: server_error
              message: internal error
  schemas:
    Error:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
      required:
        - error
    ErrorBody:
      type: object
      properties:
        code:
          type: string
          example: bad_request
          description: Machine-readable code. See Errors.
        message:
          type: string
          example: 2 fields failed validation
          description: Human-readable summary.
        fields:
          type: array
          items:
            $ref: '#/components/schemas/ErrorField'
          description: Every failing field, when the error is about the body.
      required:
        - code
        - message
    ErrorField:
      type: object
      properties:
        path:
          type: string
          example: candidates[0].created_at
          description: Path of the failing field.
        problem:
          type: string
          example: timestamp must include an explicit offset
          description: Description of the validation failure.
      required:
        - path
        - problem
  securitySchemes:
    ApiKey:
      type: apiKey
      name: Authorization
      in: header
      description: >-
        Proposed API key sent as the whole Authorization header value.
        Long-lived credential scope, handoff and rotation need confirmation.
        Per-endpoint scopes are not currently enforced.

````