> ## 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 preference (DPO) job

> Start preference training: for each prompt the dataset gives a preferred and a rejected answer, and the model learns to favour the first. Accepted in state JOB_STATE_QUEUED and runs in the background, like a supervised job.

## The dataset

Each row is a prompt and two answers, one preferred and one rejected. See
[Fine-tuning](/fine-tuning#dataset-format) for the exact format. A file of plain
conversations validates fine as a dataset -- validation checks JSON Lines, not shape -- and
is refused when the job starts, before anything is billed.


## OpenAPI

````yaml openapi/prentis-training.openapi.yaml POST /accounts/{account}/dpoJobs
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}/dpoJobs:
    post:
      tags:
        - training
      summary: Create a preference (DPO) job
      description: >-
        Start preference training: for each prompt the dataset gives a preferred
        and a rejected answer, and the model learns to favour the first.
        Accepted in state JOB_STATE_QUEUED and runs in the background, like a
        supervised job.
      operationId: createDpoJob
      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/01K7AY0M2N4P6R8T0V2X4Z6B8D
                jobId: 01K7AY0M2N4P6R8T0V2X4Z6B8D
                jobType: JOB_TYPE_DPO
                state: JOB_STATE_QUEUED
                baseModel: accounts/maas/models/your-base-model
                inputDatasetVersion: accounts/my-account/datasets/support-preferences/versions/1
                config:
                  outputModelId: support-dpo-v1
                  learningRate: 0.00001
                  epochs: 1
                  dpoBeta: '0.1'
                execution:
                  thirdParty: false
                createTime: '2026-10-10T09: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/dpoJobs \
              -H "Authorization: Bearer $PRENTIS_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
                "baseModel": "accounts/maas/models/your-base-model",
                "inputDatasetVersion": "accounts/my-account/datasets/support-preferences",
                "config": {
                  "outputModelId": "support-dpo-v1",
                  "epochs": 1,
                  "learningRate": "1e-5",
                  "dpoBeta": "0.1"
                }
              }'
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.