openapi: 3.1.0
info:
  title: iPanic API
  version: 1.0.0-draft.1
  description: Submit an iPhone panic log, retrieve the JSON result and PDF report, and check organization usage.
servers:
  - url: https://sandbox.ipanicanalyzer.com/api/v1
    description: Remote synthetic sandbox
  - url: https://ipanic-api-sandbox-production.up.railway.app/api/v1
    description: Original sandbox endpoint
security:
  - partnerKey: []
tags:
  - name: Analyses
  - name: Usage
paths:
  /analyses:
    post:
      tags: [Analyses]
      operationId: createAnalysis
      summary: Submit an iPhone panic file
      description: >
        Each organization and environment has its own idempotency namespace.
        Within 24 hours, replaying the same key and equivalent input returns
        the original analysis ID. Reusing a key with different input returns 409.
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[A-Za-z0-9_-]+$'}
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              additionalProperties: false
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: One UTF-8 .ips panic file, up to 1 MiB. The sandbox accepts its published synthetic samples only.
                language:
                  $ref: '#/components/schemas/Language'
                  default: en
      responses:
        '202':
          description: Accepted. An identical replay can also return this original receipt.
          headers:
            Location:
              schema: {type: string, format: uri-reference}
              description: Relative URL for polling the analysis.
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Receipt'
              example: {id: an_example001, statusUrl: /api/v1/analyses/an_example001}
        '400': {$ref: '#/components/responses/Error'}
        '401': {$ref: '#/components/responses/Error'}
        '403': {$ref: '#/components/responses/Error'}
        '409': {$ref: '#/components/responses/Error'}
        '413': {$ref: '#/components/responses/Error'}
        '415': {$ref: '#/components/responses/Error'}
        '422': {$ref: '#/components/responses/Error'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Error'}
  /analyses/{analysisId}:
    parameters:
      - $ref: '#/components/parameters/AnalysisId'
    get:
      tags: [Analyses]
      operationId: getAnalysis
      summary: Check status or retrieve the diagnosis
      responses:
        '200':
          description: Current state. Check status before reading result or failure. Completed reports expire after 24 hours or when the allocation ends, whichever comes first.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Analysis'
        '401': {$ref: '#/components/responses/Error'}
        '403': {$ref: '#/components/responses/Error'}
        '404': {$ref: '#/components/responses/Error'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /analyses/{analysisId}/report.pdf:
    parameters:
      - $ref: '#/components/parameters/AnalysisId'
    get:
      tags: [Analyses]
      operationId: downloadAnalysisPdf
      summary: Download the PDF for a completed analysis
      description: Uses the saved diagnosis and its language. Downloads do not consume analysis credits.
      responses:
        '200':
          description: iPanic-branded PDF attachment.
          headers:
            Content-Disposition:
              schema: {type: string}
            Cache-Control:
              schema: {type: string, const: 'private, no-store'}
          content:
            application/pdf:
              schema: {type: string, format: binary}
        '401': {$ref: '#/components/responses/Error'}
        '403': {$ref: '#/components/responses/Error'}
        '404': {$ref: '#/components/responses/Error'}
        '409': {$ref: '#/components/responses/Error'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/Error'}
  /usage:
    get:
      tags: [Usage]
      operationId: getUsage
      summary: Read the current organization quota
      responses:
        '200':
          description: Usage in the active allocation period.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Usage'
        '401': {$ref: '#/components/responses/Error'}
        '403': {$ref: '#/components/responses/Error'}
        '429': {$ref: '#/components/responses/RateLimited'}
components:
  securitySchemes:
    partnerKey:
      type: http
      scheme: bearer
      description: Organization API key for this environment. Keep it in the integration backend.
  headers:
    RetryAfter:
      description: Seconds to wait before the next request. Omitted when polling is unnecessary.
      schema: {type: integer, minimum: 1}
  parameters:
    AnalysisId:
      name: analysisId
      in: path
      required: true
      schema: {type: string, pattern: '^an_[A-Za-z0-9_-]+$'}
  responses:
    Error:
      description: Request failed. Use error.code to decide how to proceed.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Error'}
    RateLimited:
      description: Request rate or concurrency limit reached.
      headers:
        Retry-After: {$ref: '#/components/headers/RetryAfter'}
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Error'}
  schemas:
    Language:
      type: string
      enum: [en, es, pt-br]
    Receipt:
      type: object
      required: [id, statusUrl]
      properties:
        id: {type: string}
        statusUrl: {type: string, format: uri-reference}
    Analysis:
      type: object
      required: [id, status, language, createdAt]
      properties:
        id: {type: string}
        status: {type: string, enum: [queued, processing, completed, failed]}
        language: {$ref: '#/components/schemas/Language'}
        createdAt: {type: string, format: date-time}
        expiresAt: {type: string, format: date-time}
        result: {$ref: '#/components/schemas/Diagnosis'}
        reportPdfUrl: {type: string, format: uri-reference}
        failure: {$ref: '#/components/schemas/Failure'}
      allOf:
        - if:
            properties:
              status: {const: completed}
          then:
            required: [result, reportPdfUrl, expiresAt]
          else:
            not:
              anyOf:
                - required: [result]
                - required: [reportPdfUrl]
        - if:
            properties:
              status: {const: failed}
          then:
            required: [failure, expiresAt]
          else:
            not:
              required: [failure]
    Diagnosis:
      type: object
      required: [device, panic, summary, evidence, components, steps, diagnosticVersion]
      properties:
        device:
          type: object
          required: [model, iosVersion]
          properties:
            model: {type: [string, 'null']}
            iosVersion: {type: [string, 'null']}
        panic:
          type: object
          required: [type, signature]
          properties:
            type: {type: [string, 'null']}
            signature: {type: [string, 'null']}
        summary:
          type: object
          required: [subsystem, priorityComponent, confidence, difficulty, description]
          properties:
            subsystem: {type: [string, 'null']}
            priorityComponent: {type: [string, 'null']}
            confidence: {type: string, enum: [low, medium, high, undetermined]}
            difficulty: {enum: [low, medium, high, null]}
            description: {type: string}
        evidence:
          type: object
          required: [referenceStatus]
          properties:
            referenceStatus: {type: string, enum: [matched, conflicting, unmatched]}
        components:
          type: array
          items:
            type: object
            required: [name, reason, rank]
            properties:
              name: {type: string}
              reason: {type: string}
              rank: {type: integer, minimum: 1}
        steps:
          type: array
          items: {type: string}
        diagnosticVersion:
          type: string
          description: Identifies the diagnostic pipeline revision that generated this result.
    Usage:
      type: object
      required: [periodStart, periodEnd, included, reserved, consumed, remaining]
      properties:
        periodStart: {type: string, format: date-time}
        periodEnd: {type: string, format: date-time}
        included: {type: integer, minimum: 0}
        reserved: {type: integer, minimum: 0}
        consumed: {type: integer, minimum: 0}
        remaining: {type: integer, minimum: 0}
    Failure:
      type: object
      required: [code, message]
      properties:
        code: {type: string}
        message: {type: string}
    Error:
      type: object
      required: [requestId, error]
      properties:
        requestId: {type: string}
        error: {$ref: '#/components/schemas/Failure'}
