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

# Create a supervised fine-tuning job

> Start supervised fine-tuning: the model learns to answer the way the last assistant turn of each example does. The job is accepted in state JOB_STATE_QUEUED and runs in the background -- a 201 means it has been accepted, not that it has started. If the job cannot be allowed to run (for example, your organization has not accepted the data processing addendum), it is refused here and none of your data leaves.

## What happens after the 201

The job is accepted, not started. It moves `QUEUED` → `RUNNING` → `SUCCEEDED`, and on success
`outputModel` names the new model in your account, which you serve on a dedicated deployment
(see [Fine-tuning](/fine-tuning#5-serve-it)). Poll
[Get a supervised fine-tuning job](/api-reference/get-supervised-fine-tuning-job), or watch it
in the console's **Training** page -- it is the same job.

Send an `Idempotency-Key` and a retried request returns the job the first one created
instead of starting a second, billable run.


## OpenAPI

````yaml openapi/prentis-training.openapi.yaml POST /accounts/{account}/supervisedFineTuningJobs
openapi: 3.1.0
info:
  title: Prentis Training API
  version: 0.1.0
  summary: >-
    Fine-tune models on your own data, with the same API key you use for
    inference.
  description: >
    Upload a dataset, start a fine-tuning job on it, and watch it train -- from
    code, with

    the API key you already use for inference. The jobs and datasets are the
    same ones the

    console shows: anything started here appears there, and the other way round.


    Every path is under `/v1/accounts/{account}`, where `{account}` is the
    account the key

    belongs to. A key reaches its own account only.


    A finished job produces a model in your account
    (`accounts/{account}/models/{outputModelId}`),

    listed under Custom models in the console. To call it, deploy it on a
    dedicated deployment

    and send inference requests to the deployment
    (`accounts/{account}/deployments/{deployment}`).
servers:
  - url: https://compute.prentis.ai/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: training
  - name: datasets
paths:
  /accounts/{account}/supervisedFineTuningJobs:
    post:
      tags:
        - training
      summary: Create a supervised fine-tuning job
      description: >-
        Start supervised fine-tuning: the model learns to answer the way the
        last assistant turn of each example does. The job is accepted in state
        JOB_STATE_QUEUED and runs in the background -- a 201 means it has been
        accepted, not that it has started. If the job cannot be allowed to run
        (for example, your organization has not accepted the data processing
        addendum), it is refused here and none of your data leaves.
      operationId: createSupervisedFineTuningJob
      parameters:
        - $ref: '#/components/parameters/Account'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TrainingJobDraft'
      responses:
        '201':
          headers:
            x-request-id:
              $ref: '#/components/headers/x-request-id'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrainingJob'
              example:
                name: accounts/my-account/trainingJobs/01K7AX3V9Q2M8T4R6Y0B1C5D7E
                jobId: 01K7AX3V9Q2M8T4R6Y0B1C5D7E
                jobType: JOB_TYPE_SFT
                state: JOB_STATE_QUEUED
                baseModel: accounts/maas/models/your-base-model
                inputDatasetVersion: accounts/my-account/datasets/support-chats/versions/1
                config:
                  outputModelId: support-lora-v1
                  loraRank: 16
                  learningRate: 0.0001
                  epochs: 2
                estimatedCost:
                  amount: '1.93'
                  currency: USD
                execution:
                  thirdParty: false
                createTime: '2026-10-10T08:00:00Z'
          description: Created.
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '402':
          $ref: '#/components/responses/Error402'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '409':
          $ref: '#/components/responses/Error409'
        '429':
          $ref: '#/components/responses/Error429'
        '500':
          $ref: '#/components/responses/Error500'
        '503':
          $ref: '#/components/responses/Error503'
      x-codeSamples:
        - lang: curl
          label: cURL
          source: >-
            curl
            https://compute.prentis.ai/v1/accounts/my-account/supervisedFineTuningJobs
            \
              -H "Authorization: Bearer $PRENTIS_API_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: support-lora-v1" \
              -d '{
                "baseModel": "accounts/maas/models/your-base-model",
                "inputDatasetVersion": "accounts/my-account/datasets/support-chats",
                "config": {
                  "outputModelId": "support-lora-v1",
                  "epochs": 2,
                  "learningRate": "1e-4",
                  "loraRank": 16
                }
              }'
        - lang: python
          label: Python
          source: >-
            import os

            import requests


            BASE = "https://compute.prentis.ai/v1/accounts/my-account"

            HEADERS = {"Authorization": f"Bearer
            {os.environ['PRENTIS_API_KEY']}"}


            job = requests.post(
                f"{BASE}/supervisedFineTuningJobs",
                headers={**HEADERS, "Idempotency-Key": "support-lora-v1"},
                json={
                    "baseModel": "accounts/maas/models/your-base-model",
                    "inputDatasetVersion": "accounts/my-account/datasets/support-chats",
                    "config": {"outputModelId": "support-lora-v1", "epochs": 2, "learningRate": "1e-4"},
                },
            )

            job.raise_for_status()

            print(job.json()["name"], job.json()["state"])
components:
  parameters:
    Account:
      name: account
      in: path
      required: true
      schema:
        type: string
        maxLength: 64
      description: >-
        Your account id -- the one in the console's address bar (my-account for
        this key). It must be the account the key belongs to.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
      description: >-
        Your own key for making a create safe to retry (up to 255 characters).
        Sending the same key again returns the job the first request created
        instead of starting a second one.
  schemas:
    TrainingJobDraft:
      type: object
      required:
        - baseModel
        - inputDatasetVersion
        - config
      properties:
        baseModel:
          type: string
          maxLength: 256
          description: >-
            The model to fine-tune, as a full resource name
            (accounts/maas/models/your-base-model). Only base models offered for
            training are accepted -- the console's Training page lists them.


            The model to fine-tune, as a full resource name
            (accounts/maas/models/your-base-model). Only base models offered for
            training are accepted -- the console's Training page lists them.


            The model to fine-tune, as a full resource name
            (accounts/maas/models/your-base-model). Only base models offered for
            training are accepted -- the console's Training page lists them.


            The model to fine-tune, as a full resource name
            (accounts/maas/models/your-base-model). Only base models offered for
            training are accepted -- the console's Training page lists them.
        inputDatasetVersion:
          type: string
          maxLength: 256
          description: >-
            The training data: a dataset (accounts/{account}/datasets/{dataset})
            to use its latest version, or one version (…/versions/{n}) to pin
            it. The dataset must be READY.


            The preference data: a dataset to use its latest version, or one
            version to pin it. Each row needs a prompt and two answers -- see
            the dataset format guide. The dataset must be READY.


            The training data: a dataset (accounts/{account}/datasets/{dataset})
            to use its latest version, or one version (…/versions/{n}) to pin
            it. The dataset must be READY.


            The training data: a dataset (accounts/{account}/datasets/{dataset})
            to use its latest version, or one version (…/versions/{n}) to pin
            it. The dataset must be READY.
        evaluationDatasetVersion:
          type: string
          maxLength: 256
          description: >-
            Accepted so that the body of a create can be sent unchanged, but not
            used: the estimate covers the training data only.


            Optional held-out data to measure evaluation loss on, in the same
            form as inputDatasetVersion. Leave it empty and set
            config.evalAutoCarveoutBasisPoints to hold out a share of the
            training data instead, or leave both empty for no evaluation.


            Accepted so that the body of a create can be sent unchanged, but not
            used: the estimate covers the training data only.


            Optional held-out data to measure evaluation loss on, in the same
            form as inputDatasetVersion. Leave it empty and set
            config.evalAutoCarveoutBasisPoints to hold out a share of the
            training data instead, or leave both empty for no evaluation.
        config:
          $ref: '#/components/schemas/TrainingConfig'
          description: >-
            How to train. outputModelId is required: the id of the model the job
            will create, which must not already exist in your account. The rest
            are optional -- loraRank, learningRate (a string, e.g. "1e-4"),
            epochs (1-100), batchSize, maxContextLength (longer examples are
            skipped and counted, never truncated), earlyStop,
            evalAutoCarveoutBasisPoints (0-9999, in hundredths of a percent),
            lrSchedule (constant, cosine or linear_decay), warmupSteps, and
            dpoBeta (preference jobs only; a string, default "0.1"). Fixed once
            the job is created.


            How to train. outputModelId is required: the id of the model the job
            will create, which must not already exist in your account. The rest
            are optional -- loraRank, learningRate (a string, e.g. "1e-4"),
            epochs (1-100), batchSize, maxContextLength (longer examples are
            skipped and counted, never truncated), earlyStop,
            evalAutoCarveoutBasisPoints (0-9999, in hundredths of a percent),
            lrSchedule (constant, cosine or linear_decay), warmupSteps, and
            dpoBeta (preference jobs only; a string, default "0.1"). Fixed once
            the job is created.


            How to train. outputModelId is required: the id of the model the job
            will create, which must not already exist in your account. The rest
            are optional -- loraRank, learningRate (a string, e.g. "1e-4"),
            epochs (1-100), batchSize, maxContextLength (longer examples are
            skipped and counted, never truncated), earlyStop,
            evalAutoCarveoutBasisPoints (0-9999, in hundredths of a percent),
            lrSchedule (constant, cosine or linear_decay), warmupSteps, and
            dpoBeta (preference jobs only; a string, default "0.1"). Fixed once
            the job is created.


            How to train. outputModelId is required: the id of the model the job
            will create, which must not already exist in your account. The rest
            are optional -- loraRank, learningRate (a string, e.g. "1e-4"),
            epochs (1-100), batchSize, maxContextLength (longer examples are
            skipped and counted, never truncated), earlyStop,
            evalAutoCarveoutBasisPoints (0-9999, in hundredths of a percent),
            lrSchedule (constant, cosine or linear_decay), warmupSteps, and
            dpoBeta (preference jobs only; a string, default "0.1"). Fixed once
            the job is created.
    TrainingJob:
      type: object
      properties:
        name:
          type: string
        jobId:
          type: string
        jobType:
          type: string
          enum:
            - JOB_TYPE_SFT
            - JOB_TYPE_DPO
        state:
          type: string
          enum:
            - JOB_STATE_QUEUED
            - JOB_STATE_RUNNING
            - JOB_STATE_CHECKPOINTING
            - JOB_STATE_FINALIZING
            - JOB_STATE_SUCCEEDED
            - JOB_STATE_FAILED
            - JOB_STATE_CANCELLED
            - JOB_STATE_EXPIRED
        stateDetail:
          type: string
        errorClass:
          type: string
        baseModel:
          type: string
        inputDatasetVersion:
          type: string
        evaluationDatasetVersion:
          type: string
        outputModel:
          type: string
        config:
          $ref: '#/components/schemas/TrainingConfig'
        progress:
          $ref: '#/components/schemas/JobProgress'
        estimatedCost:
          allOf:
            - $ref: '#/components/schemas/Money'
        execution:
          $ref: '#/components/schemas/JobExecution'
        createdBy:
          type: string
        createTime:
          type: string
          format: date-time
        startTime:
          type: string
          format: date-time
        completeTime:
          type: string
          format: date-time
    TrainingConfig:
      type: object
      required:
        - outputModelId
      properties:
        outputModelId:
          type: string
          maxLength: 64
        loraRank:
          type: integer
        learningRate:
          type: string
        epochs:
          type: integer
        batchSize:
          type: integer
        maxContextLength:
          type: integer
        earlyStop:
          type: boolean
        evalAutoCarveoutBasisPoints:
          type: integer
          minimum: 0
          maximum: 9999
        lrSchedule:
          type: string
        warmupSteps:
          type: integer
        dpoBeta:
          type: string
    JobProgress:
      type: object
      properties:
        percent:
          type: integer
          minimum: 0
          maximum: 100
        epoch:
          type: integer
        totalEpochs:
          type: integer
        processedTokens:
          type: string
          format: int64
        updateTime:
          type: string
          format: date-time
        step:
          type: string
          format: int64
        totalSteps:
          type: string
          format: int64
        recent:
          type: array
          maxItems: 200
          items:
            $ref: '#/components/schemas/TrainingMetricPoint'
        skippedExamples:
          type: string
          format: int64
    Money:
      type: object
      properties:
        amount:
          type: string
        currency:
          type: string
    JobExecution:
      type: object
      properties:
        providerDisplayName:
          type: string
        thirdParty:
          type: boolean
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
            - type
            - code
          properties:
            message:
              type: string
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - permission_error
                - not_found_error
                - rate_limit_error
                - server_error
                - service_unavailable
                - billing_error
            code:
              type: string
              enum:
                - INVALID_REQUEST
                - INVALID_API_KEY
                - OPERATION_NOT_ALLOWED
                - TENANT_SUSPENDED
                - AGREEMENT_REQUIRED
                - DATA_RESIDENCY_VIOLATION
                - PROVIDER_NOT_ALLOWED
                - PROVIDER_SERVICE_UNAVAILABLE
                - INSUFFICIENT_BALANCE
                - PAYMENT_REQUIRED
                - MODEL_NOT_FOUND
                - MODEL_ALREADY_EXISTS
                - DATASET_NOT_FOUND
                - DATASET_ALREADY_EXISTS
                - DATASET_STATE_INVALID
                - DATASET_IN_USE
                - DATASET_TOO_LARGE
                - JSONL_LINE_NOT_OBJECT
                - JSONL_MISSING_FIELD
                - JSONL_ENCODING
                - JOB_NOT_FOUND
                - JOB_STATE_INVALID
                - TRAINING_RUN_ABANDONED
                - TRAINING_TOKENIZE_FAILED
                - TRAINING_BASE_NOT_EXPORTABLE
                - IDEMPOTENCY_CONFLICT
                - QUOTA_EXCEEDED
                - INTERNAL
                - STORAGE_UNAVAILABLE
            param:
              type:
                - string
                - 'null'
    TrainingMetricPoint:
      type: object
      properties:
        step:
          type: string
          format: int64
        epoch:
          type: integer
        loss:
          type: string
        evalLoss:
          type: string
        learningRate:
          type: string
        time:
          type: string
          format: date-time
  headers:
    x-request-id:
      schema:
        type: string
      required: true
      description: >-
        On every response, successes and errors alike. Quote it when you ask us
        about a call.
    Retry-After:
      schema:
        type: integer
        minimum: 0
      description: How many seconds to wait before retrying, on the responses that set it.
  responses:
    Error400:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `DATASET_STATE_INVALID`, `INVALID_REQUEST`,
        `JSONL_ENCODING`, `JSONL_LINE_NOT_OBJECT`, `JSONL_MISSING_FIELD`,
        `TRAINING_BASE_NOT_EXPORTABLE`, `TRAINING_TOKENIZE_FAILED`. See the
        Errors page for what each one means, whether it is safe to retry, and
        whether it is billed.
    Error401:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `INVALID_API_KEY`. See the Errors page for what
        each one means, whether it is safe to retry, and whether it is billed.
    Error402:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `INSUFFICIENT_BALANCE`, `PAYMENT_REQUIRED`. See
        the Errors page for what each one means, whether it is safe to retry,
        and whether it is billed.
    Error403:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `AGREEMENT_REQUIRED`,
        `DATA_RESIDENCY_VIOLATION`, `OPERATION_NOT_ALLOWED`,
        `PROVIDER_NOT_ALLOWED`, `PROVIDER_SERVICE_UNAVAILABLE`,
        `TENANT_SUSPENDED`. See the Errors page for what each one means, whether
        it is safe to retry, and whether it is billed.
    Error404:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `DATASET_NOT_FOUND`, `JOB_NOT_FOUND`,
        `MODEL_NOT_FOUND`. See the Errors page for what each one means, whether
        it is safe to retry, and whether it is billed.
    Error409:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `DATASET_ALREADY_EXISTS`, `DATASET_IN_USE`,
        `IDEMPOTENCY_CONFLICT`, `JOB_STATE_INVALID`, `MODEL_ALREADY_EXISTS`. See
        the Errors page for what each one means, whether it is safe to retry,
        and whether it is billed.
    Error429:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `QUOTA_EXCEEDED`. See the Errors page for what
        each one means, whether it is safe to retry, and whether it is billed.
    Error500:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `INTERNAL`, `TRAINING_RUN_ABANDONED`. See the
        Errors page for what each one means, whether it is safe to retry, and
        whether it is billed.
    Error503:
      headers:
        x-request-id:
          $ref: '#/components/headers/x-request-id'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      description: >-
        `error.code` is one of: `STORAGE_UNAVAILABLE`. See the Errors page for
        what each one means, whether it is safe to retry, and whether it is
        billed.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        `Authorization: Bearer <your API key>` -- the same key as inference. A
        key reaches the account it belongs to and no other.

````

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