> ## Documentation Index
> Fetch the complete documentation index at: https://docs.roofkit.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create upload session

> Creates a RoofKit-hosted photo upload link for one homeowner photo. Share the returned `url` with the homeowner as a normal link. The link opens a RoofKit page, never an iframe. When the homeowner uploads a photo, RoofKit removes the photo metadata, stores the photo privately, and starts one billable render with the style of this request. Poll the session to read the result. The link expires after `expires_in_minutes` and accepts one photo. Only the API key that creates the session can read it.


**Required scopes:** `sales_visualizer.use`


## OpenAPI

````yaml /api-reference/openapi.yaml post /api/v1/upload-sessions
openapi: 3.1.0
info:
  title: RoofKit Partner API
  version: 1.0.0
  summary: REST JSON API for external RoofKit partner integrations.
  contact:
    name: RoofKit Engineering
  description: >
    RoofKit Partner API v1 authenticates requests with workspace API keys. API
    keys bind each request to exactly one workspace; Partner API paths do not
    accept roofer_id, workspace_id, or tenant identifiers. Authenticated
    endpoints are limited to 60 requests per minute per API key, method, and
    route template; invalid authentication attempts are limited separately at 20
    requests per minute per client and endpoint. Date-time parameters and fields
    accept ISO 8601 with Z or a ±HH:MM offset, for example 2026-09-07T04:00:00Z
    or 2026-09-07T00:00:00-04:00. Both name the same instant and return the same
    result. Encode + as %2B in a query string. Responses return UTC.


    A date-time without an offset, for example 2026-09-07T04:00:00, is
    deprecated. Until the sunset on 2027-01-10T00:00:00Z it is read as UTC, and
    the response carries a `Deprecation` header (RFC 9745) and a `Sunset` header
    (RFC 8594). From the sunset, Z or a ±HH:MM offset is required. A date-time
    without an offset then returns 400 with the `VALIDATION_ERROR` code, and the
    response carries no `Deprecation` or `Sunset` header.


    Each API key holds only the scopes that a workspace owner or admin chose
    when the key was created, and its scopes never change. Each operation lists
    the scopes it requires in `x-required-scopes`. A new key in Settings starts
    on the Read-only preset: `leads.read`, `analytics.read`, `reports.read`, and
    `data_objects.read`. A Read-only key can call every read operation except
    exports, Sales Visualizer reads, and trashed Data Objects, and it cannot
    call any write operation. A call that needs a scope the key does not hold
    returns 403 `FORBIDDEN`, and `error.details.required_scope` names a missing
    scope.
servers:
  - url: https://api.roofkit.ai
    description: Canonical RoofKit Partner API origin
security:
  - ApiKeyBearer: []
tags:
  - name: Foundation
    description: Authentication and contract foundation endpoints.
  - name: Leads
    description: Partner CRM lead sync endpoints.
  - name: Attribution
    description: >-
      Partner marketing attribution, campaign analytics, and event ingestion
      endpoints.
  - name: Pipeline
    description: Partner-readable lead pipeline metadata.
  - name: Reports
    description: Partner report import, listing, fetch, export, and download endpoints.
  - name: Visualizer
    description: >-
      Partner Sales Visualizer creation, generation, hosted photo upload links,
      image reads, session history, and share-link endpoints.
  - name: Estimates
    description: Partner Budget Estimate creation and reads for the key-bound workspace.
  - name: Objects
    description: >-
      Partner Data Objects discovery, schema, records, relations, views, and
      admin endpoints.
paths:
  /api/v1/upload-sessions:
    post:
      tags:
        - Visualizer
      summary: Create upload session
      description: >
        Creates a RoofKit-hosted photo upload link for one homeowner photo.
        Share the returned `url` with the homeowner as a normal link. The link
        opens a RoofKit page, never an iframe. When the homeowner uploads a
        photo, RoofKit removes the photo metadata, stores the photo privately,
        and starts one billable render with the style of this request. Poll the
        session to read the result. The link expires after `expires_in_minutes`
        and accepts one photo. Only the API key that creates the session can
        read it.
      operationId: Create upload session
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadSessionCreateRequest'
      responses:
        '201':
          description: Upload session created.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            Idempotency-Replayed:
              $ref: '#/components/headers/IdempotencyReplayed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadSessionResponse'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/InsufficientCredits'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: >-
        A unique value for each new request, such as a UUID. Send the same value
        to retry a request safely. The same key and payload replay the first
        response with `Idempotency-Replayed: true`.
      schema:
        type: string
        minLength: 1
        maxLength: 160
  schemas:
    UploadSessionCreateRequest:
      type: object
      additionalProperties: false
      required:
        - material_name
        - color_name
      properties:
        session_id:
          type:
            - string
            - 'null'
          maxLength: 200
          description: >-
            Optional Sales Visualizer session that groups the render. Defaults
            to `upload_session:{id}`.
        material_name:
          type: string
          maxLength: 100
          example: Architectural Shingle
        color_name:
          type: string
          maxLength: 100
          example: Charcoal
        color_hex:
          type: string
          pattern: ^#[0-9A-Fa-f]{6}$
          example: '#333333'
        shape_name:
          type:
            - string
            - 'null'
          maxLength: 100
        selected_material_id:
          type:
            - string
            - 'null'
          maxLength: 150
        selected_color_id:
          type:
            - string
            - 'null'
          maxLength: 150
        selected_shape_id:
          type:
            - string
            - 'null'
          maxLength: 150
        locale:
          type: string
          enum:
            - en
            - es
          default: en
          description: Language of the homeowner upload page.
        expires_in_minutes:
          type: integer
          minimum: 5
          maximum: 1440
          default: 60
    UploadSessionResponse:
      type: object
      additionalProperties: false
      required:
        - upload_session
      properties:
        upload_session:
          $ref: '#/components/schemas/UploadSession'
    UploadSession:
      type: object
      additionalProperties: false
      required:
        - id
        - status
        - url
        - session_id
        - locale
        - expires_at
        - style
        - visualization_id
        - visualization
        - failure_code
        - uploaded_at
        - completed_at
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - awaiting_upload
            - processing
            - completed
            - failed
            - expired
        url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            Homeowner upload page. Present only while `status` is
            `awaiting_upload`.
        session_id:
          type: string
        locale:
          type: string
          enum:
            - en
            - es
        expires_at:
          type: string
          format: date-time
        style:
          $ref: '#/components/schemas/UploadSessionStyle'
        visualization_id:
          type:
            - string
            - 'null'
        visualization:
          oneOf:
            - $ref: '#/components/schemas/Visualization'
            - type: 'null'
        failure_code:
          description: >-
            Why the render did not finish. Present only when `status` is
            `failed`.
          oneOf:
            - type: string
              enum:
                - INSUFFICIENT_CREDITS
                - WORKSPACE_UNAVAILABLE
                - GENERATION_FAILED
            - type: 'null'
        uploaded_at:
          type:
            - string
            - 'null'
          format: date-time
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
            - request_id
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
            request_id:
              type: string
            details:
              type: object
              additionalProperties: true
    UploadSessionStyle:
      type: object
      additionalProperties: false
      required:
        - material_name
        - color_name
        - color_hex
        - shape_name
        - selected_material_id
        - selected_color_id
        - selected_shape_id
      properties:
        material_name:
          type: string
        color_name:
          type: string
        color_hex:
          type:
            - string
            - 'null'
        shape_name:
          type:
            - string
            - 'null'
        selected_material_id:
          type:
            - string
            - 'null'
        selected_color_id:
          type:
            - string
            - 'null'
        selected_shape_id:
          type:
            - string
            - 'null'
    Visualization:
      type: object
      additionalProperties: false
      required:
        - id
        - session_id
        - source
        - landing_id
        - landing_name
        - landing_slug
        - selected_material_id
        - selected_color_id
        - selected_shape_id
        - original_image_url
        - processed_image_url
        - processing_status
        - processing_started_at
        - processing_completed_at
        - processing_error
        - created_at
        - updated_at
      properties:
        id:
          type: string
        session_id:
          type: string
        source:
          type: string
          const: sales_visualizer
        landing_id:
          type:
            - string
            - 'null'
        landing_name:
          type:
            - string
            - 'null'
        landing_slug:
          type:
            - string
            - 'null'
        selected_material_id:
          type:
            - string
            - 'null'
        selected_color_id:
          type:
            - string
            - 'null'
        selected_shape_id:
          type:
            - string
            - 'null'
        original_image_url:
          type: string
        processed_image_url:
          type:
            - string
            - 'null'
        processing_status:
          $ref: '#/components/schemas/VisualizationProcessingStatus'
        processing_started_at:
          type:
            - string
            - 'null'
          format: date-time
        processing_completed_at:
          type:
            - string
            - 'null'
          format: date-time
        processing_error:
          type:
            - string
            - 'null'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    ErrorCode:
      type: string
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - RATE_LIMITED
        - VALIDATION_ERROR
        - NOT_FOUND
        - CONFLICT
        - INSUFFICIENT_CREDITS
        - INTERNAL_ERROR
    VisualizationProcessingStatus:
      type: string
      enum:
        - pending
        - processing
        - completed
        - failed
  headers:
    RateLimitLimit:
      description: Maximum requests allowed in the current window.
      schema:
        type: string
    RateLimitRemaining:
      description: Requests remaining in the current window.
      schema:
        type: string
    RateLimitReset:
      description: ISO timestamp when the current rate-limit window resets.
      schema:
        type: string
        format: date-time
    IdempotencyReplayed:
      description: Present and true when a stored successful write response was replayed.
      schema:
        type: string
        enum:
          - 'true'
    RetryAfter:
      description: Seconds until the caller should retry.
      schema:
        type: string
  responses:
    ValidationError:
      description: Request payload, query, or required header is invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Missing, malformed, revoked, deleted, or unknown API key.
      headers:
        WWW-Authenticate:
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InsufficientCredits:
      description: The workspace cannot cover the credit charge for this operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: >-
        The API key lacks a required scope, or the workspace is not active. A
        scope failure names a missing scope in `details.required_scope`. When
        the key lookup refuses the call, `details.missing_scopes` lists every
        required scope that the key does not hold.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            missing_scope:
              summary: A Read-only key calls a write operation
              value:
                error:
                  code: FORBIDDEN
                  message: 'API key does not include the required scope: leads.write.'
                  request_id: req_123
                  details:
                    required_scope: leads.write
                    missing_scopes:
                      - leads.write
    Conflict:
      description: Request conflicts with idempotency or external event identity.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    RateLimited:
      description: Rate limit exceeded for this key and endpoint or invalid-auth bucket.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    InternalError:
      description: Internal authentication or server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  securitySchemes:
    ApiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: RoofKit API key
      description: 'Use `Authorization: Bearer rk_live_...`.'

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.