openapi: 3.1.0
info:
  title: OnPin API
  version: 0.1.0
  description: Public API for publishing static artifacts to shareable URLs.
servers:
  - url: https://api.onpin.live
paths:
  /v0/publish:
    post:
      summary: Publish an artifact
      description: Publish a static file or bundle. Supports anonymous (no auth, 24h expiry) and authenticated (API key, no expiry) publishing.
      security:
        - bearerAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublishRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/StaticBundleMultipartPublishRequest"
            encoding:
              archive:
                contentType: application/zip
      responses:
        "201":
          description: Published
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishResponse"
        "400":
          $ref: "#/components/responses/PublishError"
        "429":
          description: Rate limited
  /v0/publish/{deployment_id}:
    get:
      summary: Get deployment status
      parameters:
        - $ref: "#/components/parameters/DeploymentId"
      responses:
        "200":
          description: Deployment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Deployment"
    put:
      summary: Update a deployment in place
      description: Replace the content of an existing deployment. The public URL stays the same. Requires the API key that created the deployment.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DeploymentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublishRequest"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/StaticBundleMultipartPublishRequest"
            encoding:
              archive:
                contentType: application/zip
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishResponse"
    delete:
      summary: Delete a deployment
      description: Removes public access and deletes stored artifacts. Requires the API key that created the deployment.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DeploymentId"
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deployment_id:
                    type: string
                  status:
                    const: deleted
  /v0/deployments:
    get:
      summary: List deployments
      description: Returns all deployments owned by the API key.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Deployments
          content:
            application/json:
              schema:
                type: object
                properties:
                  deployments:
                    type: array
                    items:
                      $ref: "#/components/schemas/Deployment"
  /v0/domains:
    post:
      summary: Add a custom domain
      description: Register a custom domain to serve deployments. Returns DNS verification records the user must add. Free plan allows 1 domain per account.
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
                  description: The domain to register (e.g. docs.yoursite.com).
      responses:
        "201":
          description: Domain registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomDomain"
        "400":
          description: Invalid domain
        "403":
          description: Domain limit reached
        "409":
          description: Domain already registered
    get:
      summary: List custom domains
      description: Returns all custom domains owned by the API key.
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Domains
          content:
            application/json:
              schema:
                type: object
                properties:
                  domains:
                    type: array
                    items:
                      $ref: "#/components/schemas/CustomDomain"
  /v0/domains/{domain_id}:
    get:
      summary: Check domain status
      description: Returns domain details including current verification status. Poll after DNS setup.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: Domain
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomDomain"
    delete:
      summary: Remove a custom domain
      description: Removes the domain and all its routes.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
  /v0/domains/{domain_id}/routes:
    post:
      summary: Add a route to a domain
      description: Map a path prefix to a deployment. Domain must be active, deployment must be live and non-expiring.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DomainId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [path_prefix, deployment_id]
              properties:
                path_prefix:
                  type: string
                  description: URL path prefix (e.g. "/" or "/docs").
                deployment_id:
                  type: string
      responses:
        "201":
          description: Route created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DomainRoute"
    get:
      summary: List routes for a domain
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DomainId"
      responses:
        "200":
          description: Routes
          content:
            application/json:
              schema:
                type: object
                properties:
                  routes:
                    type: array
                    items:
                      $ref: "#/components/schemas/DomainRoute"
  /v0/domains/{domain_id}/routes/{route_id}:
    put:
      summary: Update a route
      description: Change which deployment a route points to.
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DomainId"
        - $ref: "#/components/parameters/RouteId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [deployment_id]
              properties:
                deployment_id:
                  type: string
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DomainRoute"
    delete:
      summary: Remove a route
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/DomainId"
        - $ref: "#/components/parameters/RouteId"
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
  /v0/auth/device:
    post:
      summary: Start device authorization
      description: Request a device code and verification URL. The agent shows the URL to the user, who opens it in a browser to authorize. No authentication required.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Device code created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceCodeResponse"
  /v0/auth/device/token:
    post:
      summary: Poll for API key
      description: Poll with the device code to check if the user has authorized. Returns 428 while pending, 200 with API key once approved, 410 if expired.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [device_code]
              properties:
                device_code:
                  type: string
      responses:
        "200":
          description: Approved — API key returned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceTokenApproved"
        "428":
          description: Pending — user has not yet authorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    const: pending
        "410":
          description: Expired — device code is no longer valid
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key obtained via the device auth flow. Send as "Authorization: Bearer <api-key>".
  parameters:
    DeploymentId:
      name: deployment_id
      in: path
      required: true
      schema:
        type: string
    DomainId:
      name: domain_id
      in: path
      required: true
      schema:
        type: string
    RouteId:
      name: route_id
      in: path
      required: true
      schema:
        type: string
  responses:
    PublishError:
      description: Structured publishing error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/PublishError"
  schemas:
    PublishRequest:
      oneOf:
        - $ref: "#/components/schemas/StaticObjectPublishRequest"
        - $ref: "#/components/schemas/StaticBundlePublishRequest"
    StaticObjectPublishRequest:
      type: object
      required: [artifact_type, name]
      properties:
        artifact_type:
          const: static_object
        name:
          type: string
          description: Filename including extension. Used to infer content_type if not provided.
        content_type:
          type: string
          description: Optional. Server infers from the file extension if omitted.
        content:
          type: string
          description: File content as a UTF-8 string. Use for text files.
        content_base64:
          type: string
          description: File content as base64. Use for binary files.
        expires_in_seconds:
          type: integer
    StaticBundlePublishRequest:
      type: object
      required: [artifact_type]
      properties:
        artifact_type:
          const: static_bundle
        files:
          type: object
          description: Map of file paths to content. Use only for small bundles with a few text files. For folders and binary assets, use multipart/form-data.
          additionalProperties:
            $ref: "#/components/schemas/FilePayload"
        entrypoint:
          type: string
          description: The file to serve at the root URL. Defaults to index.html.
        expires_in_seconds:
          type: integer
    StaticBundleMultipartPublishRequest:
      type: object
      required: [artifact_type, archive]
      properties:
        artifact_type:
          const: static_bundle
        archive:
          type: string
          format: binary
          description: Raw zip archive bytes. Field name must be archive.
        entrypoint:
          type: string
          description: The file to serve at the root URL. Defaults to index.html.
        expires_in_seconds:
          type: integer
    FilePayload:
      type: object
      properties:
        content:
          type: string
        content_base64:
          type: string
        content_type:
          type: string
          description: Optional. Server infers from the filename if omitted.
    PublishResponse:
      type: object
      properties:
        deployment_id:
          type: string
        status:
          $ref: "#/components/schemas/DeploymentStatus"
        public_url:
          type: string
        expires_at:
          type: string
          format: date-time
          description: Present for anonymous deployments. Absent for authenticated deployments (no expiry).
    Deployment:
      type: object
      properties:
        id:
          type: string
        artifactType:
          type: string
        anonymous:
          type: boolean
        platformPath:
          type: string
        status:
          $ref: "#/components/schemas/DeploymentStatus"
        publicUrl:
          type: string
        expiresAt:
          type: string
          format: date-time
        provider:
          type: string
    DeploymentStatus:
      enum: [validating, building, publishing, live, failed, expired, deleted]
    PublishError:
      type: object
      required: [status, error_code, message, fix, docs_url]
      properties:
        status:
          const: failed
        error_code:
          type: string
        message:
          type: string
        fix:
          type: string
        docs_url:
          type: string
          description: Agent-readable recovery docs.
    DeviceCodeResponse:
      type: object
      properties:
        device_code:
          type: string
          description: Opaque token for polling. Do not show to the user.
        user_code:
          type: string
          description: Human-readable code embedded in the verification URL.
        verification_url:
          type: string
          description: URL to show the user. They open it in a browser to authorize.
        expires_in:
          type: integer
          description: Seconds until the device code expires (600 = 10 minutes).
        interval:
          type: integer
          description: Recommended polling interval in seconds.
    DeviceTokenApproved:
      type: object
      properties:
        status:
          const: approved
        api_key:
          type: string
          description: The API key. Store to disk immediately and never print.
        api_key_id:
          type: string
    CustomDomain:
      type: object
      properties:
        id:
          type: string
          description: Domain ID (prefixed with cd_).
        domain:
          type: string
        status:
          type: string
          enum: [pending, active, failed, removing]
        verification:
          type: object
          description: DNS verification record to add at your registrar.
          properties:
            type:
              type: string
            name:
              type: string
            value:
              type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    DomainRoute:
      type: object
      properties:
        id:
          type: string
          description: Route ID (prefixed with dr_).
        domainId:
          type: string
        pathPrefix:
          type: string
          description: URL path prefix (e.g. "/" or "/docs").
        deploymentId:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
