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

# Create an Agent Picks self-run upload

> Open an upload for a bundle the agentpicks CLI wrote on the customer's own machine. The body is the bundle's manifest.json, which declares each artifact's name, bytes and sha256. The response gives one signed Storage address per declared artifact: PUT the artifact's bytes to its url within two hours, then complete the upload. A url can't replace an artifact already stored: Storage refuses the PUT, and a retry returns a null url for each artifact already stored, so it PUTs only the rest. The upload expires 24 hours after it is created unless completed. A retry with the same runId returns the same paths with fresh addresses and restarts the 24 hours; a retry for a completed upload returns it with no addresses. Refusals carry a reason: 400 invalid_manifest (the bundle format rejects it), repository_remote_missing (a repository run whose repository has no remote URL; add an origin remote and run it again), duplicate_artifact, artifact_too_large (diff.patch over 800,000 bytes, source-files.json over 1,048,576, any other artifact over 50 MiB; details name the artifact, its bytes and the limit), unknown_category, unknown_request or unknown_stack; 403 plan_excludes_uploads (the account's Agent Picks plan is not Local Runner, Cloud or Enterprise), project_uploads_off (on Local Runner, uploads are off for this project; a member who manages billing and is on the project turns them on in the project's Agent Picks settings), analysis_cap_reached (the project has recorded as many self-runs this UTC calendar month as its plan analyses: 500 on Local Runner, no cap on Cloud or Enterprise; deleting a run doesn't give one back; details name the limit and resets_at, the start of the next UTC month), forbidden or paused; 409 run_exists (another project or a hosted test uses this run id), upload_expired (the upload expired and its artifacts are still being removed; create it again once a cleanup sweep, which runs every 15 minutes, has removed its artifacts, no sooner than 2 hours 15 minutes after it was last created or retried) or upload_deleted (the upload was deleted and its artifacts are still being removed; create it again once a cleanup sweep, which runs every 15 minutes, has removed its artifacts, no sooner than 2 hours 15 minutes after it was last created or retried). The member who created the API key needs project management permission and edit rights on the project; otherwise 403. Not available over MCP: devtune_get_agent_picks_upload reads an upload's status. Requires Agent Picks. A body that is not valid JSON, or an empty body, is refused with 400 bad_request and no reason. Requires the `agent_picks.run` scope.



## OpenAPI

````yaml /openapi.json post /projects/{projectId}/agent-picks/uploads
openapi: 3.1.0
info:
  title: DevTune API
  version: 2.0.0
  description: >-
    API for programmatic access to your AI visibility data, webhook
    subscriptions, and automation workflows. Use this API to integrate DevTune
    data into CI/CD pipelines, BI tools, AI agents, and operational systems.
servers:
  - url: https://devtune.ai/api/v2
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Connection and usage
    description: >-
      The project a credential is bound to, its scopes and features, usage, and
      code-mode receipts.
  - name: Setup
    description: >-
      Brands, domains, brand terms, platforms, content sources and pausing a
      project.
  - name: Prompts and topics
    description: Tracked prompts, their topics, and prompt-set analysis.
  - name: Visibility
    description: >-
      Visibility metrics, AI answers, sentiment, competitive position and the
      project timeline.
  - name: Citations
    description: >-
      Citation statistics, the most-cited domains and pages, and cited lookalike
      sites.
  - name: Positioning
    description: How well AI answers match your positioning statements.
  - name: Accuracy
    description: Claims AI answers make about your brand, and contradictions.
  - name: Content gaps
    description: Topics, sources and brands where competitors are cited.
  - name: AI Traffic
    description: >-
      AI referrals, answer fetches, crawls, 404 Demand, Direct lift and Search
      Console totals.
  - name: Adoption
    description: npm downloads and GitHub stars for project domains.
  - name: Audit and pages
    description: Audit findings, Google eligibility, page metrics, and locked pages.
  - name: Actions
    description: Suggested and accepted work, briefs, drafts and pull requests.
  - name: Results
    description: Content changes, measured results and what works.
  - name: Agents
    description: Managed agents, their schedules and runs.
  - name: Dex
    description: Private Dex conversations, specialist handoffs and memory.
  - name: Agent Picks
    description: Agent Picks setup, tests, schedules and results.
  - name: Library and Knowledge
    description: Library items and the Knowledge Profile.
  - name: Webhooks
    description: Webhook subscriptions and signing secrets.
  - name: Agent registration
    description: Setup drafts for an unclaimed agent registration.
paths:
  /projects/{projectId}/agent-picks/uploads:
    post:
      tags:
        - Agent Picks
      summary: Create an Agent Picks self-run upload
      description: >-
        Open an upload for a bundle the agentpicks CLI wrote on the customer's
        own machine. The body is the bundle's manifest.json, which declares each
        artifact's name, bytes and sha256. The response gives one signed Storage
        address per declared artifact: PUT the artifact's bytes to its url
        within two hours, then complete the upload. A url can't replace an
        artifact already stored: Storage refuses the PUT, and a retry returns a
        null url for each artifact already stored, so it PUTs only the rest. The
        upload expires 24 hours after it is created unless completed. A retry
        with the same runId returns the same paths with fresh addresses and
        restarts the 24 hours; a retry for a completed upload returns it with no
        addresses. Refusals carry a reason: 400 invalid_manifest (the bundle
        format rejects it), repository_remote_missing (a repository run whose
        repository has no remote URL; add an origin remote and run it again),
        duplicate_artifact, artifact_too_large (diff.patch over 800,000 bytes,
        source-files.json over 1,048,576, any other artifact over 50 MiB;
        details name the artifact, its bytes and the limit), unknown_category,
        unknown_request or unknown_stack; 403 plan_excludes_uploads (the
        account's Agent Picks plan is not Local Runner, Cloud or Enterprise),
        project_uploads_off (on Local Runner, uploads are off for this project;
        a member who manages billing and is on the project turns them on in the
        project's Agent Picks settings), analysis_cap_reached (the project has
        recorded as many self-runs this UTC calendar month as its plan analyses:
        500 on Local Runner, no cap on Cloud or Enterprise; deleting a run
        doesn't give one back; details name the limit and resets_at, the start
        of the next UTC month), forbidden or paused; 409 run_exists (another
        project or a hosted test uses this run id), upload_expired (the upload
        expired and its artifacts are still being removed; create it again once
        a cleanup sweep, which runs every 15 minutes, has removed its artifacts,
        no sooner than 2 hours 15 minutes after it was last created or retried)
        or upload_deleted (the upload was deleted and its artifacts are still
        being removed; create it again once a cleanup sweep, which runs every 15
        minutes, has removed its artifacts, no sooner than 2 hours 15 minutes
        after it was last created or retried). The member who created the API
        key needs project management permission and edit rights on the project;
        otherwise 403. Not available over MCP: devtune_get_agent_picks_upload
        reads an upload's status. Requires Agent Picks. A body that is not valid
        JSON, or an empty body, is refused with 400 bad_request and no reason.
        Requires the `agent_picks.run` scope.
      operationId: createAgentPicksUpload
      parameters:
        - name: projectId
          in: path
          required: true
          description: >-
            The project the credential is bound to. Read it from GET
            /connection.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentPicksBundleManifest'
      responses:
        '200':
          description: Opened upload
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/AgentPicksCreatedUploadData'
                  meta:
                    $ref: '#/components/schemas/Meta'
                required:
                  - data
                  - meta
        '400':
          $ref: '#/components/responses/BadRequestError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          $ref: '#/components/responses/ForbiddenError'
        '404':
          $ref: '#/components/responses/NotFoundError'
        '409':
          $ref: '#/components/responses/ConflictError'
        '429':
          $ref: '#/components/responses/RateLimitExceededError'
components:
  schemas:
    AgentPicksBundleManifest:
      type: object
      description: >-
        A self-run bundle's manifest.json as the agentpicks CLI writes it. Its
        context is reported by the customer's machine and not verified. It names
        stack or repository, never both.
      required:
        - bundleVersion
        - runId
        - runner
        - agent
        - host
        - sandbox
        - request
        - startedAt
        - finishedAt
        - artifacts
      properties:
        bundleVersion:
          type: integer
          enum:
            - 1
        runId:
          type: string
          format: uuid
          description: Generated by the runner; the upload is keyed on it.
        runner:
          type: object
          required:
            - name
            - version
          properties:
            name:
              type: string
              enum:
                - '@devtune/agentpicks'
            version:
              type: string
        agent:
          type: object
          required:
            - kind
            - version
            - model
          properties:
            kind:
              type: string
            version:
              type: string
            model:
              type: string
        host:
          type: object
          required:
            - os
            - arch
          properties:
            os:
              type: string
            arch:
              type: string
        sandbox:
          type: object
          required:
            - provider
            - imageDigest
            - egressIp
          properties:
            provider:
              type: string
              enum:
                - docker
            imageDigest:
              type: string
            egressIp:
              type:
                - string
                - 'null'
        stack:
          type: object
          required:
            - slug
            - starter
            - lockDigest
          properties:
            slug:
              type: string
            starter:
              type: string
            lockDigest:
              type: string
        repository:
          type: object
          required:
            - remoteUrl
            - commit
          properties:
            remoteUrl:
              type:
                - string
                - 'null'
            commit:
              type: string
        request:
          type: object
          required:
            - category
            - intent
            - prompt
          properties:
            category:
              type: string
              description: A published category's slug, or one of the project's jobs.
            intent:
              type: string
              enum:
                - add
                - recommend
                - hosted
                - improve
            prompt:
              type: string
        startedAt:
          type: string
          format: date-time
        finishedAt:
          type: string
          format: date-time
        artifacts:
          type: array
          items:
            type: object
            required:
              - name
              - bytes
              - sha256
            properties:
              name:
                type: string
              bytes:
                type: integer
                minimum: 0
              sha256:
                type: string
    AgentPicksCreatedUploadData:
      allOf:
        - $ref: '#/components/schemas/AgentPicksUploadData'
        - type: object
          required:
            - uploads
          properties:
            uploads:
              type: array
              description: >-
                One signed address per declared artifact while the upload is
                open; empty once completed.
              items:
                type: object
                required:
                  - name
                  - bytes
                  - path
                  - url
                properties:
                  name:
                    type: string
                  bytes:
                    type: integer
                  path:
                    type: string
                    description: self-run/<projectId>/<runId>/<name>
                  url:
                    type:
                      - string
                      - 'null'
                    format: uri
                    description: >-
                      PUT the artifact's bytes here within two hours. Storage
                      refuses a PUT to a path that already holds an object. null
                      when the artifact is already stored.
    Meta:
      type: object
      description: Sent beside data on every successful response that returns data.
      properties:
        timestamp:
          type: string
          format: date-time
          description: When the response was produced.
        projectId:
          type: string
          format: uuid
    AgentPicksUploadData:
      type: object
      required:
        - run_id
        - status
        - expires_at
        - completed_at
        - run
      properties:
        run_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - open
            - completed
            - expired
          description: >-
            open: artifacts are uploading. completed: the run is recorded.
            expired: not completed within 24 hours, or an artifact was stored at
            the wrong size.
        expires_at:
          type: string
          format: date-time
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
        run:
          type:
            - object
            - 'null'
          description: The recorded run; null until completed.
          required:
            - id
            - test_id
            - status
            - analysis
            - analysis_error
          properties:
            id:
              type: string
              format: uuid
            test_id:
              type: string
              format: uuid
            status:
              type: string
            analysis:
              type: string
              enum:
                - pending
                - analysed
                - failed
              description: >-
                pending until the run is analysed, then analysed. failed: the
                run could not be analysed, and nothing retries it;
                analysis_error says why.
            analysis_error:
              type:
                - string
                - 'null'
              description: Why the analysis failed; null unless analysis is failed.
    Error:
      type: object
      properties:
        error:
          type: string
          description: >-
            Machine-readable error code, such as bad_request, unauthorized,
            forbidden, not_found, conflict, payment_required,
            rate_limit_exceeded or internal_error. Some endpoints return their
            own codes, listed in their descriptions.
        message:
          type: string
          description: >-
            What went wrong, for a person to read. Do not parse it; use error
            and reason.
        reason:
          type: string
          description: >-
            Why the request was refused, for an error with more than one cause,
            such as a refused content-draft start.
        details:
          type: object
          additionalProperties: true
          description: >-
            Machine-readable facts about the refusal, such as shortfall_credits
            when an Agent Picks start is refused for insufficient_credits.
        status:
          type: integer
          description: The HTTP status code, repeated in the body.
      required:
        - error
        - message
        - status
  responses:
    BadRequestError:
      description: >-
        The request is invalid: a parameter or body field is missing, has the
        wrong type or is out of range, or a cursor doesn't match this list. The
        message says which; reason, where present, is listed in the endpoint's
        description. Fix the request before retrying, and restart a list without
        its cursor. See [Errors](/api-reference/errors).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    UnauthorizedError:
      description: >-
        The request has no valid credential: the Authorization header is missing
        or malformed, or the API key or OAuth token is invalid, expired or
        revoked. Send `Authorization: Bearer <API key>` with an active key, or
        sign in again to get a new OAuth token. See
        [Errors](/api-reference/errors).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ForbiddenError:
      description: >-
        The credential is valid but can't make this request. The message says
        which cause applies: the key or sign-in lacks the scope this endpoint
        requires (named in the message), it belongs to another project, the plan
        doesn't include API access or this feature, the project is paused (a
        paused project keeps only read scopes and setup.write), or the member a
        write acts as no longer has the role it needs. Read `GET
        /projects/{projectId}/connection` for the credential's current scopes
        and features, then use a key that has the scope, sign in again and
        approve it, or upgrade the plan. See [Errors](/api-reference/errors).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFoundError:
      description: >-
        The resource doesn't exist in this project, was deleted, or the
        credential can't see it. Check the IDs in the path, and that the
        projectId is the project the credential is bound to. See
        [Errors](/api-reference/errors).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ConflictError:
      description: >-
        The request conflicts with the resource's current state. The message,
        and reason where present, say what conflicts; the endpoint's description
        lists its reasons. Read the resource again, then change the request.
        When the body has retryable true, send the same request again. See
        [Errors](/api-reference/errors).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimitExceededError:
      description: >-
        Too many requests from this credential in the current window. The
        request was refused before it took effect, so it is safe to retry: wait
        until the time in X-RateLimit-Reset, then send it again. Endpoints that
        start agent runs have a stricter limit. See [Rate
        limits](/api-reference/rate-limits).
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/X-RateLimit-Limit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/X-RateLimit-Remaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/X-RateLimit-Reset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  headers:
    X-RateLimit-Limit:
      description: Maximum requests per window
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests remaining in current window
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Unix timestamp when the window resets
      schema:
        type: integer
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key with dtk_live_ prefix. Obtain it from API Keys in the account
        sidebar. New keys start with all supported scopes selected for the
        chosen project, and you can narrow them to specific read/write scopes as
        needed.

````

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