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

# Create custom problem

> Creates a draft and starts asynchronous preparation. Poll the custom detail endpoint until prepStatus is ready or failed. A failed enqueue can return prepStatus failed even on a successful save.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/issues/custom
openapi: 3.1.0
info:
  title: Promptster reviewer API
  version: 1.0.0
  description: >-
    Public hiring API for organization API keys (psk_live_…). Requests are
    scoped to the owning organization. Dashboard-only and candidate-only
    operations are excluded. Unknown response fields may be added; artifact
    payloads depend on kind.
servers:
  - url: https://api.promptster.ai
security:
  - orgApiKey: []
paths:
  /v1/issues/custom:
    post:
      tags:
        - Custom problems
      summary: Create custom problem
      description: >-
        Creates a draft and starts asynchronous preparation. Poll the custom
        detail endpoint until prepStatus is ready or failed. A failed enqueue
        can return prepStatus failed even on a successful save.
      operationId: postVIssuesCustom
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CustomProblemInput'
            examples:
              plainPython:
                value:
                  title: Rate limiter lets bursts through after a restart
                  taskBrief: >-
                    After a deploy, the token bucket starts full for every
                    client, so the first minute lets 10x the allowed traffic
                    through. Make the bucket state survive a restart. The hidden
                    tests exercise limiter.py directly.
                  repoUrl: https://github.com/your-org/rate-limiter
                  commitSha: 3f1c2a9
                  isPrivate: false
                  language: python
                  difficulty: standard
                  skillTags:
                    - rate-limiting
                    - persistence
                  estimatedMinutes: 60
                  testSuiteConfig:
                    setupCommand: >-
                      python3 -m venv .venv && .venv/bin/pip install -q -r
                      requirements.txt pytest
                    testCommand: .venv/bin/python -m pytest -q tests/test_hidden_limiter.py
                    testFramework: pytest
                    timeoutSeconds: 300
                    testFiles:
                      - fileName: test_hidden_limiter.py
                        destination: tests/test_hidden_limiter.py
                        content: |
                          from limiter import Limiter

                          def test_state_survives_restart(tmp_path):
                              a = Limiter(rate=1, burst=1, state_dir=tmp_path)
                              assert a.allow('c')
                              b = Limiter(rate=1, burst=1, state_dir=tmp_path)
                              assert not b.allow('c')
              ticketFlow:
                value:
                  repoUrl: https://github.com/your-org/billing-service
                  isPrivate: true
                  language: typescript
                  difficulty: standard
                  skillTags:
                    - billing
                    - webhooks
                  estimatedMinutes: 60
                  testSuiteConfig:
                    setupCommand: pnpm install --frozen-lockfile
                    testCommand: pnpm vitest run src/__tests__/eng-412.test.ts
                    testFramework: vitest
                    testFiles:
                      - fileName: eng-412.test.ts
                        destination: src/__tests__/eng-412.test.ts
                        content: |
                          import { describe, it, expect } from 'vitest';
                          import { handleRefund } from '../refunds';

                          describe('ENG-412', () => {
                            it('ignores a replayed webhook', async () => {
                              const evt = { id: 'evt_1', amount: 500 };
                              await handleRefund(evt);
                              expect(await handleRefund(evt)).toEqual({ status: 'duplicate' });
                            });
                          });
                  ticketFlow:
                    ticket:
                      key: ENG-412
                      title: Refund webhooks are processed twice
                      labels:
                        - billing
                        - bug
                      fields:
                        - label: Priority
                          value: High
                      body: >-
                        Stripe retries refund webhooks when we answer slowly,
                        and each retry issues another refund. Make refund
                        handling safe to receive more than once.
                      acceptance:
                        - >-
                          A refund webhook delivered twice refunds the customer
                          once
                    personas:
                      - id: dana
                        name: Dana Ortiz
                        role: Payments lead
                        owns: Refund policy and the Stripe integration
                        brief: >-
                          Direct and busy. Answers precisely when asked a
                          precise question.
                    requirements:
                      - id: dedupe-by-event-id
                        owner: null
                        expect: required
                        ticketImplied: true
                        text: Deduplicate on the webhook event id
                      - id: keep-for-30-days
                        owner: dana
                        expect: required
                        ticketImplied: false
                        text: >-
                          Remember processed event ids for 30 days, matching
                          Stripe's retry window
                    unavailability:
                      - personaId: dana
                        fromSec: 600
                        toSec: 900
      responses:
        '201':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  issue:
                    $ref: '#/components/schemas/Issue'
                required:
                  - issue
        '400':
          description: >-
            Invalid body/repository/ticket flow or missing/inaccessible GitHub
            token.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: Invalid body
                details:
                  - path:
                      - testSuiteConfig
                      - setupCommand
                    message: Setup must not run tests.
        '401':
          description: Missing, invalid or expired credential.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: Active subscription required.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Early access approval required, or assessment quota exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: >-
            Repository or commit not found/private, or persona brief restates a
            hidden requirement.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: brief_restates_requirement
                personaId: dana
                message: >-
                  The brief repeats wording from the hidden spec. Briefs set
                  tone, not facts.
        '429':
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Failed to save custom problem.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: Could not reach GitHub or unexpected GitHub response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: GitHub rate limit exceeded; retry shortly.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CustomProblemInput:
      type: object
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        taskBrief:
          type: string
          minLength: 1
          maxLength: 8000
        brief:
          $ref: '#/components/schemas/Brief'
        repoUrl:
          type: string
          format: uri
          description: 'GitHub repository URL: https://github.com/owner/repo'
        commitSha:
          type: string
          pattern: ^[0-9a-fA-F]{7,40}$
          description: Omit to pin the current default-branch head.
        isPrivate:
          type: boolean
          default: false
          description: >-
            Uses the organization GitHub token from Settings → GitHub access.
            Candidates receive a snapshot without credentials or history.
        language:
          type: string
          enum:
            - typescript
            - javascript
            - python
            - go
            - rust
            - java
            - ruby
            - csharp
        difficulty:
          type: string
          enum:
            - warmup
            - standard
            - deep
        skillTags:
          type: array
          items:
            type: string
            minLength: 1
          maxItems: 20
          default: []
        problemType:
          type: string
          enum:
            - bug_fix
            - feature
            - performance
          default: bug_fix
        estimatedMinutes:
          type: integer
          minimum: 1
          maximum: 600
        testSuiteConfig:
          $ref: '#/components/schemas/TestSuiteConfig'
        ticketFlow:
          $ref: '#/components/schemas/TicketFlow'
      anyOf:
        - required:
            - ticketFlow
        - required:
            - title
            - taskBrief
      description: >-
        POST creates; PUT fully replaces. language is trimmed and lowercased
        before validation. A successful save starts preparation; inspect
        issue.prepStatus, including on the initial response.
      required:
        - repoUrl
        - language
        - difficulty
        - testSuiteConfig
    Issue:
      type: object
      properties:
        id:
          type: string
        issueTitle:
          type: string
        repoUrl:
          type: string
        brokenSha:
          type: string
        taskBrief:
          type: string
        language:
          type: string
        difficulty:
          type: string
        isCustom:
          type: boolean
        isPrivate:
          type: boolean
        isTicketFlow:
          type: boolean
        status:
          type: string
        prepStatus:
          anyOf:
            - type: string
              enum:
                - pending
                - ready
                - failed
            - type: 'null'
        prepError:
          anyOf:
            - type: string
            - type: 'null'
        testSuiteVerified:
          type: boolean
        issueUrl:
          anyOf:
            - type: string
              format: uri
            - type: 'null'
      additionalProperties: true
      required:
        - id
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
        message:
          type: string
        details:
          type: array
          items: {}
        personaId:
          type: string
      additionalProperties: true
      required:
        - error
    Brief:
      type: object
      properties:
        scenario:
          type: string
          minLength: 1
          maxLength: 8000
        codebase:
          type: object
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 120
            description:
              type: string
              minLength: 1
              maxLength: 2000
            stack:
              type: array
              items:
                type: string
                minLength: 1
                maxLength: 80
              maxItems: 12
            runCommand:
              type: string
              minLength: 1
              maxLength: 200
            testCommand:
              type: string
              minLength: 1
              maxLength: 200
          additionalProperties: false
        phases:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                minLength: 1
                maxLength: 120
              goal:
                type: string
                minLength: 1
                maxLength: 1000
              tasks:
                type: array
                items:
                  type: string
                  minLength: 1
                  maxLength: 500
                maxItems: 10
              guidance:
                type: array
                items:
                  type: string
                  minLength: 1
                  maxLength: 500
                maxItems: 10
            additionalProperties: false
            required:
              - name
          maxItems: 6
        evaluation:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 500
          maxItems: 10
        groundRules:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 500
          maxItems: 10
        deliverables:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 500
          maxItems: 10
      additionalProperties: false
    TestSuiteConfig:
      type: object
      properties:
        testFiles:
          type: array
          items:
            $ref: '#/components/schemas/TestFile'
          minItems: 1
          maxItems: 20
        testCommand:
          type: string
          minLength: 1
          maxLength: 500
          description: >-
            Runs from the repository root after setup. Hidden files overwrite
            destination paths after setup. Preparation requires tests to run and
            be counted, not to pass.
        setupCommand:
          type: string
          maxLength: 500
          description: >-
            Install dependencies only. Must exit zero; commands that run test
            suites are rejected.
        testFramework:
          type: string
          maxLength: 80
        timeoutSeconds:
          type: integer
          minimum: 1
          maximum: 1800
          default: 300
        edgeCaseFiles:
          type: array
          items:
            $ref: '#/components/schemas/TestFile'
          maxItems: 20
        edgeCaseCommand:
          type: string
          maxLength: 500
        hygieneRules:
          type: array
          items:
            type: string
          maxItems: 50
      required:
        - testFiles
        - testCommand
    TicketFlow:
      type: object
      properties:
        ticket:
          $ref: '#/components/schemas/Ticket'
        personas:
          type: array
          items:
            $ref: '#/components/schemas/Persona'
          minItems: 1
          maxItems: 4
        requirements:
          type: array
          items:
            $ref: '#/components/schemas/Requirement'
          minItems: 1
          maxItems: 30
        unavailability:
          type: array
          items:
            type: object
            properties:
              personaId:
                type: string
              fromSec:
                type: integer
                minimum: 0
              toSec:
                type: integer
                minimum: 1
            required:
              - personaId
              - fromSec
              - toSec
          maxItems: 10
      description: >-
        Persona and requirement IDs must each be unique. Owners and
        unavailability personaId must refer to a persona. fromSec must precede
        toSec. With ticketFlow, the ticket replaces title and taskBrief.
      required:
        - ticket
        - personas
        - requirements
    TestFile:
      type: object
      properties:
        fileName:
          type: string
          minLength: 1
        destination:
          type: string
          minLength: 1
        content:
          type: string
          minLength: 1
      required:
        - fileName
        - destination
        - content
    Ticket:
      type: object
      properties:
        key:
          type: string
          pattern: ^[A-Z][A-Z0-9]+-\d+$
        title:
          type: string
          minLength: 1
          maxLength: 200
        body:
          type: string
          minLength: 1
          maxLength: 8000
        acceptance:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 500
          minItems: 1
          maxItems: 20
        labels:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 40
          maxItems: 10
          default: []
        fields:
          type: array
          items:
            type: object
            properties:
              label:
                type: string
                minLength: 1
                maxLength: 40
              value:
                type: string
                maxLength: 200
            required:
              - label
              - value
          maxItems: 12
          default: []
      required:
        - key
        - title
        - body
        - acceptance
    Persona:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]*$
        name:
          type: string
          minLength: 1
          maxLength: 80
        role:
          type: string
          minLength: 1
          maxLength: 80
        owns:
          type: string
          minLength: 1
          maxLength: 200
        brief:
          type: string
          maxLength: 2000
          description: >-
            Tone and habits only. Repeating hidden requirements returns 422
            brief_restates_requirement.
      required:
        - id
        - name
        - role
        - owns
    Requirement:
      type: object
      properties:
        id:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]*$
        owner:
          anyOf:
            - type: string
              description: >-
                Persona ID, or null when held by nobody. No teammate can reveal
                an unowned row; it is still evaluated and an absent required row
                is missed, with no teammate-message evidence.
            - type: 'null'
        expect:
          type: string
          enum:
            - required
            - cut
        ticketImplied:
          type: boolean
          description: >-
            Whether the ticket implies this requirement. Independent of owner
            and not an evaluation exemption.
        text:
          type: string
          minLength: 1
          maxLength: 1000
        tests:
          type: array
          items:
            type: string
            minLength: 1
          maxItems: 20
          description: >-
            Links to node:test TAP names before the colon, e.g. dedupe: handles
            retry. pytest/vitest/jest links are untested; suite pass/fail still
            applies.
      required:
        - id
        - owner
        - expect
        - ticketImplied
        - text
  securitySchemes:
    orgApiKey:
      type: http
      scheme: bearer
      description: >-
        Organization API key from Settings → API. Send Authorization: Bearer
        psk_live_… .

````

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