{
  "openapi": "3.1.0",
  "info": {
    "title": "iSomor PDF Translation API",
    "version": "1.0.0",
    "description": "Server-to-server PDF translation. Use the production API base URL and keep your approved Bearer key server-side. Supports text PDFs, en/zh, asynchronous jobs and translated/bilingual output. API allowance is separate from MCP account credits. No OCR, Office, webhook, bulk or cancellation endpoints.",
    "contact": {
      "name": "iSomor Support",
      "email": "support@tryisomor.com"
    }
  },
  "servers": [
    {
      "url": "{baseUrl}",
      "description": "Production HTTPS API base URL, including /api/isomor/v1 without a trailing slash.",
      "variables": {
        "baseUrl": {
          "default": "https://tryisomor.com/api/isomor/v1",
          "description": "Public production endpoint. Requests still require an approved Bearer key."
        }
      }
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/capabilities": {
      "get": {
        "operationId": "getCapabilities",
        "summary": "Read PDF capabilities and project-specific limits",
        "description": "Read PDF capabilities and project-specific limits Required key scope: capabilities:read.",
        "x-required-scopes": ["capabilities:read"],
        "responses": {
          "200": {
            "description": "Read PDF capabilities and project-specific limits",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    },
    "/files": {
      "post": {
        "operationId": "uploadFile",
        "summary": "Upload one unencrypted PDF containing extractable text",
        "description": "Upload one unencrypted PDF containing extractable text. This does not start translation. Required key scope: files:write.",
        "x-required-scopes": ["files:write"],
        "responses": {
          "201": {
            "description": "Upload one unencrypted PDF containing extractable text. This does not start translation.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadedFile"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": ["file"],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/translations": {
      "post": {
        "operationId": "createTranslation",
        "summary": "Accept an asynchronous translation and reserve project credits",
        "description": "Accept an asynchronous translation and reserve project credits. Reuse the same Idempotency-Key and parameters for retries within 24 hours; 202 does not mean completed. Required key scope: translations:write.",
        "x-required-scopes": ["translations:write"],
        "responses": {
          "202": {
            "description": "Accept an asynchronous translation and reserve project credits. Reuse the same Idempotency-Key and parameters for retries within 24 hours; 202 does not mean completed.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Translation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[\\x21-\\x7e]+$"
            },
            "description": "Persist before submitting. Unknown outcome beyond 24 hours: contact support; do not resubmit blindly."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TranslationRequest"
              }
            }
          }
        }
      }
    },
    "/translations/{job_id}": {
      "get": {
        "operationId": "getTranslation",
        "summary": "Read job status",
        "description": "Read job status. Poll queued/running jobs no faster than Retry-After. Stop polling on failed. Required key scope: translations:read.",
        "x-required-scopes": ["translations:read"],
        "responses": {
          "200": {
            "description": "Read job status. Poll queued/running jobs no faster than Retry-After. Stop polling on failed.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Translation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            }
          }
        ]
      }
    },
    "/translations/{job_id}/results": {
      "get": {
        "operationId": "getTranslationResults",
        "summary": "Get translated/bilingual PDF links after success and settlement",
        "description": "Get translated/bilingual PDF links after success and settlement. Links expire after 15 minutes; request new links here instead of creating another job. Required key scope: translations:read.",
        "x-required-scopes": ["translations:read"],
        "responses": {
          "200": {
            "description": "Get translated/bilingual PDF links after success and settlement. Links expire after 15 minutes; request new links here instead of creating another job.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Results"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        },
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            }
          }
        ]
      }
    },
    "/quota": {
      "get": {
        "operationId": "getQuota",
        "summary": "Read independent API credits",
        "description": "Read independent API credits. remaining = limit - used - reserved; a read does not reserve credits. No automatic paid overage. Required key scope: quota:read.",
        "x-required-scopes": ["quota:read"],
        "responses": {
          "200": {
            "description": "Read independent API credits. remaining = limit - used - reserved; a read does not reserve credits. No automatic paid overage.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/X-Request-ID"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "Cache-Control": {
                "$ref": "#/components/headers/Cache-Control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quota"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error400"
          },
          "401": {
            "$ref": "#/components/responses/Error401"
          },
          "403": {
            "$ref": "#/components/responses/Error403"
          },
          "404": {
            "$ref": "#/components/responses/Error404"
          },
          "409": {
            "$ref": "#/components/responses/Error409"
          },
          "413": {
            "$ref": "#/components/responses/Error413"
          },
          "422": {
            "$ref": "#/components/responses/Error422"
          },
          "429": {
            "$ref": "#/components/responses/Error429"
          },
          "503": {
            "$ref": "#/components/responses/Error503"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key supplied after approval, not a website session or MCP OAuth token. Use only with the configured API origin."
      }
    },
    "schemas": {
      "Capabilities": {
        "type": "object",
        "properties": {
          "source_languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["auto", "en", "zh"]
            }
          },
          "target_languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["en", "zh"]
            }
          },
          "input_mime_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["application/pdf"]
            }
          },
          "output_formats": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["translated", "bilingual"]
            },
            "minItems": 1
          },
          "credits_per_page": {
            "type": "integer",
            "minimum": 1
          },
          "limits": {
            "type": "object",
            "properties": {
              "max_file_bytes": {
                "type": "integer",
                "minimum": 1
              },
              "max_pages": {
                "type": "integer",
                "minimum": 1
              },
              "max_concurrent_jobs": {
                "type": "integer",
                "minimum": 1
              },
              "requests_per_minute": {
                "type": "integer",
                "minimum": 1
              },
              "max_storage_bytes": {
                "type": "integer",
                "minimum": 1
              }
            },
            "required": [
              "max_file_bytes",
              "max_pages",
              "max_concurrent_jobs",
              "requests_per_minute",
              "max_storage_bytes"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "source_languages",
          "target_languages",
          "input_mime_types",
          "output_formats",
          "credits_per_page",
          "limits"
        ],
        "additionalProperties": false
      },
      "UploadedFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "filename": {
            "type": "string"
          },
          "media_type": {
            "type": "string",
            "enum": ["application/pdf"]
          },
          "page_count": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": ["id", "filename", "media_type", "page_count"],
        "additionalProperties": false
      },
      "TranslationRequest": {
        "type": "object",
        "properties": {
          "file_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "target_language": {
            "type": "string",
            "enum": ["en", "zh"]
          },
          "source_language": {
            "type": "string",
            "enum": ["auto", "en", "zh"],
            "default": "auto"
          },
          "pages": {
            "type": ["string", "null"],
            "maxLength": 256,
            "pattern": "^\\d+(-\\d+)?(,\\d+(-\\d+)?)*$",
            "description": "Omit or use null for all pages. One-based inclusive ranges, for example 1-3,5. Must fit the PDF page count. Repeated pages are counted once; output follows original page order."
          },
          "output_formats": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["translated", "bilingual"]
            },
            "minItems": 1,
            "default": ["translated", "bilingual"],
            "description": "Repeated formats are normalized to a unique list."
          }
        },
        "required": ["file_id", "target_language"],
        "additionalProperties": false,
        "not": {
          "anyOf": [
            {
              "required": ["source_language", "target_language"],
              "properties": {
                "source_language": {
                  "const": "en"
                },
                "target_language": {
                  "const": "en"
                }
              }
            },
            {
              "required": ["source_language", "target_language"],
              "properties": {
                "source_language": {
                  "const": "zh"
                },
                "target_language": {
                  "const": "zh"
                }
              }
            }
          ]
        }
      },
      "Translation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "file_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "status": {
            "type": "string",
            "enum": ["queued", "running", "succeeded", "failed"]
          },
          "error": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "enum": ["translation_failed"]
                  },
                  "message": {
                    "type": "string"
                  }
                },
                "required": ["code", "message"],
                "additionalProperties": false
              }
            ]
          }
        },
        "required": ["id", "file_id", "status"],
        "additionalProperties": false
      },
      "Results": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "format": {
                  "type": "string",
                  "enum": ["translated", "bilingual"]
                },
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^https://",
                  "description": "Sensitive, temporary download URL. Never log it or send the API key to this URL."
                },
                "expires_at": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "required": ["format", "url", "expires_at"],
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 2
          }
        },
        "required": ["job_id", "files"],
        "additionalProperties": false
      },
      "Quota": {
        "type": "object",
        "properties": {
          "unit": {
            "type": "string",
            "enum": ["credits"]
          },
          "limit": {
            "type": "integer",
            "minimum": 0
          },
          "used": {
            "type": "integer",
            "minimum": 0
          },
          "reserved": {
            "type": "integer",
            "minimum": 0
          },
          "remaining": {
            "type": "integer",
            "minimum": 0
          },
          "expires_at": {
            "type": ["string", "null"],
            "format": "date-time",
            "description": "Null means no configured expiry, not unlimited credits."
          }
        },
        "required": ["unit", "limit", "used", "reserved", "remaining", "expires_at"],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_api_key",
                  "insufficient_scope",
                  "invalid_request",
                  "not_found",
                  "idempotency_conflict",
                  "result_not_ready",
                  "job_failed",
                  "file_too_large",
                  "unsupported_pdf",
                  "quota_exceeded",
                  "quota_expired",
                  "concurrency_limit",
                  "rate_limited",
                  "storage_limit",
                  "service_unavailable"
                ]
              },
              "message": {
                "type": "string"
              }
            },
            "required": ["code", "message"],
            "additionalProperties": false
          },
          "request_id": {
            "type": "string"
          }
        },
        "required": ["error", "request_id"],
        "additionalProperties": false
      }
    },
    "headers": {
      "X-Request-ID": {
        "description": "Support identifier. Do not attach keys, document contents or signed URLs to support requests.",
        "schema": {
          "type": "string"
        }
      },
      "Retry-After": {
        "description": "Delay in seconds when present. Pending jobs use 5 seconds. Observe this value before retrying.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "Cache-Control": {
        "schema": {
          "type": "string",
          "const": "no-store"
        }
      }
    },
    "responses": {
      "Error400": {
        "description": "invalid_request",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error401": {
        "description": "invalid_api_key",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error403": {
        "description": "insufficient_scope or quota_expired",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error404": {
        "description": "not_found",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error409": {
        "description": "idempotency_conflict, result_not_ready or job_failed",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error413": {
        "description": "file_too_large",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error422": {
        "description": "unsupported_pdf",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error429": {
        "description": "quota_exceeded, rate_limited, concurrency_limit or storage_limit",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Error503": {
        "description": "service_unavailable",
        "headers": {
          "X-Request-ID": {
            "$ref": "#/components/headers/X-Request-ID"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "Cache-Control": {
            "$ref": "#/components/headers/Cache-Control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
