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

# Upload a file to a project

> Sends a file to the machine holding your project and attaches it under
`parentId` in the parts tree.

Send this to `dataPlane.url`, not to the control-plane host, with
`Authorization: Bearer {dataPlane.token}`. Supply the session's own
`dataPlane.connectionId` as `connectionId`; the upload is applied through the
session's live connection, so any other value is rejected.

Bodies up to 2 GB are accepted and streamed rather than buffered.




## OpenAPI

````yaml /openapi/davinci-public.v2.yaml post /api/v1/file/upload/{ownerId}/{userId}/{projectId}/{connectionId}/{parentId}
openapi: 3.1.0
info:
  title: Davinci Public API
  version: 2.0.0
  description: >
    Public Routing Server API contract for Davinci integrations and official
    SDKs.


    Most operations live on the canonical `/api/v2/projects` mount and accept a

    personal access token (`dav_ak_live_…` / `dav_ak_test_…`) or browser access

    token. Evaluation-run creation is the deliberate exception: it accepts only
    a

    `purpose=automation` grant issued to a running Code execution.

    Project membership and role are checked on every user call, and an API key
    is

    additionally bounded by the permission ceiling derived from its scopes — a
    key

    can never exceed what its owner can do, and usually does less.


    Collaboration operations (invitations, membership changes, ownership
    transfer)

    are deliberately absent: they require a browser session and are not part of
    the

    programmatic surface.


    ## Two planes


    Most operations are **control plane**: they run on the Routing Server, take

    small requests, and answer quickly. A few are **data plane**, marked

    `x-plane: data`, and go straight to the machine holding your project — the
    only

    practical way to move multi-gigabyte files or hold a connection open for
    half

    an hour.


    Data-plane operations use a different host and a different credential.
    Create a

    session on a project and it returns both: a `dataPlane.url` to send to and a

    short-lived `dataPlane.token` (`dav_gr_…`, a *grant*) to send with. A grant

    reaches exactly one project, carries no more permission than the key that

    minted it, expires in minutes, and dies when its session closes. It is not

    interchangeable with an API key in either direction.


    The data-plane host never names a specific machine, so a project that moves

    between machines mid-session stays reachable at the same URL.
servers:
  - url: https://davinci-app.com
    description: Production (control plane)
  - url: https://davinci-app.com/data
    description: >-
      Production (data plane). Serves the operations marked `x-plane: data`,
      using a session's grant rather than an API key.
security:
  - ApiKeyBearer: []
tags:
  - name: Projects
    description: >-
      Create, inspect, open, close, and delete projects, and manage their
      attached files.
  - name: Content
    description: >
      Read and write the objects a project is made of. These reach the running

      project rather than storage, so they see uncommitted work — and so they
      open

      the project if it is not already open.
  - name: Branches
    description: >
      Branches, commits, and the history of a single object. A commit captures
      the

      running project's state, so these operations need the branch loaded, which
      they

      arrange for the caller.
  - name: Sessions
    description: >
      A session opens a project, holds it open, and issues the grant that
      reaches the

      data plane. It is the programmatic equivalent of having a project open in
      a

      browser tab: while it lives, the project stays loaded.


      It is also where the agent lives. Send it a prompt, read its status from
      the

      session, read the transcript back, and stop a run that is going the wrong
      way.
  - name: AgentModels
    description: >
      Chat models available to your account. The returned keys are the values
      accepted

      by the session message API; availability follows your subscription,
      tenant, and

      any profile assigned to your account.
  - name: Tools
    description: >
      Invoke one of the agent's tools yourself, without the agent. Useful when
      you

      already know the operation you want and do not need a model to choose it —
      and

      when you want a deterministic result rather than an interpreted one.


      A tool is still held to the permissions of the credential invoking it, so
      a

      key cannot reach through a tool to something its scopes exclude.
  - name: Files
    description: >
      Data-plane file transfer. These reach your project's machine directly
      using a

      session's grant, which is what allows request bodies far larger than the

      control plane accepts.
  - name: Usage
    description: >
      What your models cost, broken down by model, project, session, or day.
      These

      read the usage ledger, which records one event per completed provider call
      —

      not your credit balance, which is `/api/v2/billing`.


      Usage is reported on an interval rather than per call, so a run that is
      still

      going may not be fully counted yet. Every response carries `asOf`, the
      moment

      the newest counted event was recorded, so you can see how current a total
      is

      instead of guessing.


      This is separate from `projects:read` on purpose: a key that reads your

      project data is not thereby allowed to see what you spend.
  - name: Evaluations
    description: |
      Lifecycle for the ephemeral target sandbox an evaluation runs against.
      Creation is available only to a `purpose=automation` grant issued to a
      running Code execution, so a sandbox always belongs to code rather than to
      a person's key. It returns a second, `purpose=evaluation` grant bound to
      the new sandbox; it never upgrades or replaces the source credential.
  - name: Tests
    description: >
      Asynchronously run an authored Test. The Test executes its linked Code

      object and applies its native evaluation criteria to derive the verdict.

      This is one way to start Code, not a prerequisite for anything the Code
      can

      do: the same function called directly holds the same authority.
  - name: Organizations
    description: Discover organizations available as containers for team planning.
  - name: Cards
    description: >
      Project-scoped agile cards, their index hierarchy, comments, tags,
      assignees,

      card dependencies, and design-object references. Project access is checked

      on every call; an inaccessible project is reported as not found.
  - name: Teams
    description: >
      Organization-scoped team boards, linked projects, workflow columns,
      sprints,

      analytics, and card placements. Team visibility is privacy preserving:

      inaccessible organizations, private teams, and cross-organization ids are

      reported as not found rather than revealing that they exist.
paths:
  /api/v1/file/upload/{ownerId}/{userId}/{projectId}/{connectionId}/{parentId}:
    parameters:
      - $ref: '#/components/parameters/DataPlaneOwnerId'
      - $ref: '#/components/parameters/DataPlaneUserId'
      - $ref: '#/components/parameters/DataPlaneProjectId'
      - $ref: '#/components/parameters/ConnectionId'
      - $ref: '#/components/parameters/ParentId'
    post:
      tags:
        - Files
      summary: Upload a file to a project
      description: >
        Sends a file to the machine holding your project and attaches it under

        `parentId` in the parts tree.


        Send this to `dataPlane.url`, not to the control-plane host, with

        `Authorization: Bearer {dataPlane.token}`. Supply the session's own

        `dataPlane.connectionId` as `connectionId`; the upload is applied
        through the

        session's live connection, so any other value is rejected.


        Bodies up to 2 GB are accepted and streamed rather than buffered.
      operationId: uploadFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/UploadFileRequest'
      responses:
        '200':
          description: The file was stored and attached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadFileResponse'
        '401':
          $ref: '#/components/responses/GrantRejected'
        '403':
          description: The grant does not carry `documents:write` on this project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '409':
          $ref: '#/components/responses/ProjectMoved'
        '413':
          description: The file exceeds the maximum upload size.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - GrantBearer: []
      x-codeSamples:
        - lang: typescript
          label: TypeScript SDK
          source: |
            import { readFile } from 'node:fs/promises';
            import { DavinciClient } from '@celedon/davinci-sdk';

            const client = new DavinciClient({
              apiKey: process.env.DAVINCI_API_KEY!,
            });

            const session = await client.sessions.create(projectId);
            const result = await session.upload({
              file: await readFile('housing.step'),
              filename: 'housing.step',
              parentId: rootId,
            });
            console.log(result.fileId);
        - lang: python
          label: Python SDK
          source: |
            import os
            from davinci_sdk import DavinciClient

            with DavinciClient(api_key=os.environ["DAVINCI_API_KEY"]) as client:
                session = client.sessions.create(project_id)
                with open("housing.step", "rb") as handle:
                    result = session.upload(handle, filename="housing.step", parent_id=root_id)
                print(result.file_id)
components:
  parameters:
    DataPlaneOwnerId:
      name: ownerId
      in: path
      required: true
      schema:
        type: string
        minLength: 1
      description: >
        Id of the account or organization that owns the project, from the
        project's

        `ownerId`.
    DataPlaneUserId:
      name: userId
      in: path
      required: true
      schema:
        type: string
        minLength: 1
      description: >
        Acting user id. Ignored for authorization — the acting identity comes
        from the

        grant, so this cannot be used to act as someone else.
    DataPlaneProjectId:
      name: projectId
      in: path
      required: true
      schema:
        type: string
        minLength: 1
      description: >
        Project id, compound as `{projectId}--{branchName}`. Must be the project
        the

        session opened.
    ConnectionId:
      name: connectionId
      in: path
      required: true
      schema:
        type: string
        pattern: ^headless-
      description: >
        The session's `dataPlane.connectionId`, which is `headless-{sessionId}`.
        Send

        what the session gave you and this is the only value you will ever need.


        What the server actually requires is the `headless-` prefix, and the

        difference matters in one place: a Code object addresses the data plane
        with

        `headless-exec-{executionId}`, because an execution holds a grant rather
        than

        a session. The prefix is what makes the upload be applied through a
        client, so

        the reference joins the parts tree instead of the bytes landing in
        storage

        with nothing pointing at them. An id naming no live connection is served
        by an

        ephemeral one.
    ParentId:
      name: parentId
      in: path
      required: true
      schema:
        type: string
        minLength: 1
      description: Id of the parts-tree object the file is attached under.
  schemas:
    UploadFileRequest:
      type: object
      required:
        - file
      properties:
        file:
          type: string
          format: binary
        objectName:
          type: string
          description: Display name for the created object. Defaults to the filename.
        keepAsArchive:
          type: boolean
          default: false
          description: |
            Preserve a ZIP or 7z upload as one archive reference instead of
            extracting it immediately. Set this when the archive will be passed
            to `session.extract_archive()` later.
      additionalProperties: false
    UploadFileResponse:
      type: object
      description: >
        The objects the upload created, keyed by id.


        One file does not always mean one object. A STEP assembly becomes a
        tree,

        a PDF becomes a document plus a page image per page, and a source file

        becomes a code object — so the answer is a map rather than a single

        record. A plain file that needs no conversion yields exactly one entry.
      required:
        - object
      properties:
        object:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/UploadedObject'
      additionalProperties: true
    ErrorEnvelope:
      description: >
        The error shape for every failure on this API, whether it was refused by
        the

        credential layer before reaching a domain or by the domain itself.


        Branch on `code`, which is stable. `message` is for a person reading a
        log or

        a dialog and may be reworded. `details` carries fields specific to one

        failure — a project-limit refusal reports its numbers there — and is
        absent

        when there are none.
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          const: false
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
            details:
              type: object
              additionalProperties: true
          additionalProperties: true
      additionalProperties: true
    UploadedObject:
      type: object
      description: >
        One object in the parts tree. Fields beyond these vary by `type`, so
        treat

        this as the shape you can rely on rather than the whole record.
      required:
        - id
        - name
        - type
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        type:
          type: string
          description: |
            What the file became: `reference` for a stored file, `geometry` or
            `assembly` for imported CAD, `code` for an imported source file.
        parent:
          type: string
          description: Id of the object this was attached under.
        children:
          type: array
          items:
            type: string
        fileType:
          type: string
          description: |
            Extension of the stored blob, including the leading dot. Present on
            objects that own a file. Concatenated with `id` this gives the
            `fileName` that downloads it.
        fileSize:
          type: integer
          minimum: 0
      additionalProperties: true
  responses:
    GrantRejected:
      description: >
        The grant is missing, unknown, expired, or revoked. These are one
        response so a

        rejected request cannot be used to learn which grants exist. Refresh the

        session to get a new grant, or create a new session if that also fails.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    ProjectMoved:
      description: >
        The project moved to another machine. Error code is `PROJECT_MOVED`.
        Retry the

        request: the next one resolves to the machine holding it now, at the
        same URL.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RateLimited:
      description: Request rate exceeded, either globally or for this key.
      headers:
        Retry-After:
          schema:
            type: integer
            minimum: 1
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    ApiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: dav_ak_live_... or dav_ak_test_...
      x-default: dav_ak_live_your_token
      description: >
        Personal access token. The scopes granted at issuance determine both
        which

        operations the key may call and the ceiling on the resource permissions
        it

        can exercise. Each scope family is a ladder: `projects:manage` implies

        `projects:write` and `projects:read`; `cards:manage` implies
        `cards:write`

        and `cards:read`; and `teams:manage` implies `teams:write` and
        `teams:read`.

        These are named API capability bundles, not blanket domain roles:

        `manage` exposes only the permissions enumerated for that scope and
        never

        bypasses the acting user's current role-based access.
    GrantBearer:
      type: http
      scheme: bearer
      bearerFormat: dav_gr_...
      x-default: dav_gr_your_grant
      description: >
        A session's data-plane grant, from `dataPlane.token`. Valid only on the

        data-plane host, only for the one project its session opened, and only
        for

        minutes.


        A grant is not an API key and the two are not interchangeable:
        presenting a

        grant to a control-plane operation is rejected, and an API key is not
        accepted

        on the data plane. The separation is deliberate — a grant is meant to be

        cheap to lose, so it reaches one project, does no more than the key that

        minted it, and is revoked when its session closes.

````