{
  "openapi": "3.0.3",
  "info": {
    "title": "ResumeJSON — Resume & CV Parser API",
    "version": "0.1.0",
    "description": "A resume parser API for resume data extraction: resume or CV in, typed JSON out, not OCR text. Parse resume PDF files, DOCX, plain text, or a photo or scan of the page, and get back name, contact details, every role with normalised dates, education, skills, certifications and languages. Median parse under two seconds. A scanned or photographed CV has no text layer, so the model reads the pages as images — that works, and takes a few seconds rather than two.",
    "contact": {
      "name": "ResumeJSON support",
      "url": "https://resumejson.tiraisoft.com/docs"
    }
  },
  "servers": [
    {
      "url": "https://resumejson.tiraisoft.com"
    }
  ],
  "tags": [
    {
      "name": "parsing",
      "description": "Resume and CV extraction"
    }
  ],
  "paths": {
    "/v1/parse": {
      "post": {
        "tags": [
          "parsing"
        ],
        "summary": "Parse a resume into structured JSON",
        "description": "Accepts a JSON body with a `text` field, a multipart form with a `file` part, or the file bytes as the raw request body. A PDF without a text layer — a scan or a photograph — is read as images instead, and so is an uploaded JPEG, PNG or WebP; `meta.read` tells you which happened. PDF, DOCX and image types are detected from the bytes, not from the Content-Type header. Text values come back in the CV's own language unless you ask for one with `output_language`.",
        "operationId": "parseResume",
        "parameters": [
          {
            "name": "output_language",
            "in": "query",
            "required": false,
            "description": "Write the extracted text values in this language, up to 40 characters — a name (`French`, `Bahasa Indonesia`, `日本語`) or a code (`fr`, `pt-BR`, `zh-Hans`). Any language the model can write is accepted; there is no fixed list. Omit it and the CV is returned in its own language, which is the default and what this endpoint has always done.\n\nJob titles, the headline, highlights, skills, degrees, locations and proficiency wording are translated. Names of people, companies and schools are not, and neither are emails, phone numbers, URLs or dates. `meta.outputLanguage` echoes back what was understood.\n\nA JSON or multipart body may carry the same value as an `output_language` field instead; when both are sent the query parameter wins.",
            "schema": {
              "type": "string",
              "maxLength": 40,
              "example": "French"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "The resume as plain text."
                  },
                  "output_language": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 40,
                    "description": "Same as the query parameter of that name; the query parameter wins if both are sent."
                  }
                }
              },
              "example": {
                "text": "Jane Doe\nSenior Engineer\njane@example.com\n\nEXPERIENCE\nAcme — Engineer, 2020 to Present"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PDF, DOCX, .txt, or a JPEG/PNG/WebP image of the resume."
                  },
                  "output_language": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Same as the query parameter of that name; the query parameter wins if both are sent."
                  }
                }
              }
            },
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/jpeg": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/webp": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The parsed resume.",
            "headers": {
              "X-Parse-Ms": {
                "description": "Server-side parse time in milliseconds.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Request-Id": {
                "description": "Identifies this call. Quote it to support — including about a parse that came back wrong, not only one that failed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "400": {
            "description": "The body carried no resume, or `output_language` was not a language.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Either the subscription is not active — lapsed, cancelled or never started — in which case RapidAPI refuses the call at its gateway and the body is `{ \"message\": \"…\" }` with no `error.code`; or the call did not come through RapidAPI at all, which this worker refuses as `not_via_rapidapi`. Branch on the presence of `error.code`, not on the status.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "$ref": "#/components/schemas/GatewaySubscriptionError"
                    }
                  ]
                }
              }
            }
          },
          "405": {
            "description": "`/v1/parse` is POST only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Document larger than the limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "File type cannot be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "File unreadable, or the document is not a resume.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Sent by RapidAPI, not the parser, so the body is `{ \"message\": \"…\" }` with no `error.code`; branch on the message. Two causes: (1) the message names the MONTHLY quota — your plan's quota is spent; do not retry, wait for `x-ratelimit-requests-reset` or upgrade the plan. (2) the message is `Too many requests` — a short burst limit; back off for a few seconds and retry.",
            "headers": {
              "x-ratelimit-requests-limit": {
                "description": "Your plan's monthly request quota.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-requests-remaining": {
                "description": "Requests left in the current window.",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-requests-reset": {
                "description": "Seconds until the window resets.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayQuotaError"
                }
              }
            }
          },
          "500": {
            "description": "Our fault: an unhandled failure (`internal`), or the deployment is missing a secret (`gate_misconfigured`). It still costs you a request: RapidAPI meters every call it forwards to us, whatever we answer — measured 2026-09-16, a `400` decremented `x-ratelimit-requests-remaining` by the same one as a `200`. Do not retry the same request; quote `request_id` — also in `X-Request-Id` — to support. The message is fixed on purpose: a runtime exception string is ours, not yours.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifies this call. The same value as `error.request_id`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The model answered with something unusable. Retry once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Model upstream unavailable. Retry after the Retry-After delay.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "parsing"
        ],
        "summary": "Liveness and the deployed version",
        "operationId": "health",
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ParseResponse": {
        "type": "object",
        "required": [
          "resume",
          "meta"
        ],
        "properties": {
          "resume": {
            "$ref": "#/components/schemas/Resume"
          },
          "meta": {
            "type": "object",
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "text",
                  "pdf",
                  "docx",
                  "image"
                ],
                "description": "What you sent, as detected from the bytes rather than your content-type header. A scanned PDF is `pdf`, like any other PDF; `image` appears only when you upload a JPEG, PNG or WebP."
              },
              "read": {
                "type": "string",
                "enum": [
                  "text",
                  "vision"
                ],
                "description": "How the document was read. `text` means it had a text layer. `vision` means it did not — a scan or a photograph — and the model read the pages as images, which takes roughly twice as long."
              },
              "characters": {
                "type": "integer",
                "nullable": true,
                "description": "Characters of text parsed, or null when `read` is `vision` and there was no text layer to count."
              },
              "outputLanguage": {
                "type": "string",
                "nullable": true,
                "description": "The language that was asked for, resolved — send `fr` and this reads `French`. null when `output_language` was omitted and the CV was returned in its own language."
              },
              "durationMs": {
                "type": "integer"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "input_tokens": {
                    "type": "integer"
                  },
                  "output_tokens": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "Resume": {
        "type": "object",
        "required": [
          "basics",
          "work",
          "education",
          "skills",
          "certifications",
          "languages",
          "total_years_experience"
        ],
        "properties": {
          "basics": {
            "type": "object",
            "properties": {
              "full_name": {
                "type": "string",
                "nullable": true
              },
              "email": {
                "type": "string",
                "nullable": true
              },
              "phone": {
                "type": "string",
                "nullable": true
              },
              "location": {
                "type": "string",
                "nullable": true
              },
              "headline": {
                "type": "string",
                "nullable": true
              },
              "links": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "work": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkEntry"
            }
          },
          "education": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EducationEntry"
            }
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "certifications": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "nullable": true
                },
                "issuer": {
                  "type": "string",
                  "nullable": true
                },
                "date": {
                  "type": "string",
                  "nullable": true,
                  "description": "YYYY-MM or YYYY"
                }
              }
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "language": {
                  "type": "string"
                },
                "proficiency": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "total_years_experience": {
            "type": "number",
            "nullable": true,
            "description": "Computed from `work`, not read off the document. Months are counted inclusively, a role still in progress runs to today, a year-only range spans January to December, and roles held at the same time are counted once rather than added together. null when no role carries a usable start date."
          }
        }
      },
      "WorkEntry": {
        "type": "object",
        "properties": {
          "company": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "start_date": {
            "type": "string",
            "nullable": true,
            "description": "YYYY-MM, or YYYY when no month is stated."
          },
          "end_date": {
            "type": "string",
            "nullable": true,
            "description": "null when the role is current."
          },
          "is_current": {
            "type": "boolean"
          },
          "location": {
            "type": "string",
            "nullable": true
          },
          "highlights": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "EducationEntry": {
        "type": "object",
        "properties": {
          "institution": {
            "type": "string",
            "nullable": true
          },
          "degree": {
            "type": "string",
            "nullable": true
          },
          "field_of_study": {
            "type": "string",
            "nullable": true
          },
          "start_date": {
            "type": "string",
            "nullable": true
          },
          "end_date": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "GatewaySubscriptionError": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "example": "You are not subscribed to this API."
          }
        }
      },
      "GatewayQuotaError": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Names the MONTHLY quota when the plan is spent (final), or reads `Too many requests` for a burst (retry after a short back-off).",
            "example": "You have exceeded the MONTHLY quota for Requests on your current plan, BASIC."
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "bad_request",
                  "empty_input",
                  "too_large",
                  "unsupported_type",
                  "unsupported_language",
                  "unreadable_file",
                  "not_a_resume",
                  "upstream_unavailable",
                  "upstream_invalid",
                  "not_via_rapidapi",
                  "gate_misconfigured",
                  "method_not_allowed",
                  "not_found",
                  "internal"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string",
                "description": "Present on a 500. The same value as the `X-Request-Id` header, which every response to this endpoint carries. Quote it to support and the call can be found; without it a support mail can only describe a request, which is why this exists (resumejson#39)."
              }
            }
          }
        }
      }
    }
  }
}