{
  "openapi": "3.1.0",
  "info": {
    "title": "igscraper External API",
    "version": "v1",
    "description": "Public Bearer-key API for programmatic igscraper integrations. This OpenAPI document intentionally covers a stable subset of the runtime /api/v1 endpoint surface."
  },
  "servers": [
    {
      "url": "/",
      "description": "Current host"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Jobs",
      "description": "Submit jobs and read job status/results."
    }
  ],
  "paths": {
    "/api/v1/jobs": {
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Submit a job",
        "description": "Submit a scraping job for one external user in your API key namespace.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Optional idempotency key for safe retries of the exact same request payload."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobSubmitRequest"
              },
              "examples": {
                "followers": {
                  "summary": "Followers job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "followers",
                    "params": {
                      "usernames": [
                        "nasa"
                      ],
                      "max_followers": 50
                    }
                  }
                },
                "following": {
                  "summary": "Following job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "following",
                    "params": {
                      "usernames": [
                        "nasa"
                      ],
                      "max_following": 50
                    }
                  }
                },
                "likers": {
                  "summary": "Likers job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "likers",
                    "params": {
                      "usernames": [
                        "nasa"
                      ],
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                },
                "emails": {
                  "summary": "Emails job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "emails",
                    "params": {
                      "email_source_type": "followers",
                      "usernames": [
                        "nasa"
                      ],
                      "max_emails": 500,
                      "max_followers": 50,
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                },
                "phones": {
                  "summary": "Phones job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "phones",
                    "params": {
                      "phone_source_type": "followers",
                      "usernames": [
                        "nasa"
                      ],
                      "max_phones": 500,
                      "max_followers": 50,
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                },
                "hashtag": {
                  "summary": "Hashtag job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "hashtag",
                    "params": {
                      "hashtag": "fitness",
                      "hashtag_content_type": "recent",
                      "hashtag_scrape_type": "posters",
                      "max_results": 100
                    }
                  }
                },
                "location": {
                  "summary": "Location job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "location",
                    "params": {
                      "location_id": "213385402",
                      "location_query": "miami",
                      "location_content_type": "recent",
                      "location_scrape_type": "posters",
                      "max_results": 100
                    }
                  }
                },
                "singlePostLikers": {
                  "summary": "Single post likers job",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "single-post-likers",
                    "params": {
                      "post_url": "https://www.instagram.com/p/ABC123/",
                      "max_results": 100
                    }
                  }
                },
                "emailsHashtagPosters": {
                  "summary": "Emails from hashtag posters",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "emails",
                    "params": {
                      "email_source_type": "hashtag_posters",
                      "hashtag": "fitness",
                      "max_emails": 300,
                      "max_followers": 50,
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                },
                "emailsLikers": {
                  "summary": "Emails from likers",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "emails",
                    "params": {
                      "email_source_type": "likers",
                      "usernames": [
                        "nasa"
                      ],
                      "max_emails": 300,
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                },
                "phonesHashtagPosters": {
                  "summary": "Phones from hashtag posters",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "phones",
                    "params": {
                      "phone_source_type": "hashtag_posters",
                      "hashtag": "fitness",
                      "max_phones": 300,
                      "max_followers": 50,
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                },
                "phonesLikers": {
                  "summary": "Phones from likers",
                  "value": {
                    "external_user_id": "customer-123",
                    "tool_type": "phones",
                    "params": {
                      "phone_source_type": "likers",
                      "usernames": [
                        "nasa"
                      ],
                      "max_phones": 300,
                      "max_posts": 12,
                      "max_new_posts": 2,
                      "max_total_likers": 0
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replay response when idempotency key maps to an existing job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitResponse"
                }
              }
            }
          },
          "201": {
            "description": "Job accepted and queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobSubmitResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/SubmitConflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "operationId": "submitJob"
      }
    },
    "/api/v1/jobs/{job_id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get job",
        "description": "Get one job by id within your namespace.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdPathParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Job fetched successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobListItem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "operationId": "getJob"
      }
    },
    "/api/v1/jobs/{job_id}/enrich": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get enrichment preflight",
    "description": "Get retroactive enrichment eligibility and reservation preview for a completed job. When both add-ons are omitted or false, returns eligible=false with reason_code NO_ADDON_SELECTED.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdPathParam"
          },
          {
            "name": "enrich_profiles",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Select the profile-fields add-on for retroactive enrichment. It adds fields such as biography, email, phone, website, and business."
          },
          {
            "name": "enrich_country_date",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Select the country/date add-on for retroactive enrichment."
          }
        ],
        "responses": {
          "200": {
            "description": "Preflight evaluated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpgradePreflightResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "operationId": "getJobEnrichmentPreflight"
      },
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Start enrichment upgrade",
    "description": "Start a retroactive enrichment upgrade for a completed job. Partially enriched jobs can request missing supported add-ons. Emails and phones support retroactive country/date only. At least one add-on must be selected. When request-body add-on flags are provided, they must be JSON booleans.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdPathParam"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Select the profile-fields add-on for retroactive enrichment. It adds fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Select the country/date add-on for retroactive enrichment."
                  }
                },
            "additionalProperties": true
          }
        }
      }
    },
    "responses": {
      "200": {
        "description": "Requested add-ons are already covered by the source job; replayed success response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpgradeStartResponse"
                }
              }
            }
          },
      "202": {
        "description": "Enrichment upgrade accepted and queued, or a queued/processing upgrade already covers the requested add-ons.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpgradeStartResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/JobEnrichmentConflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "operationId": "startJobEnrichmentUpgrade"
      }
    },
    "/api/v1/jobs/{job_id}/results": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get job results (JSON)",
        "description": "Read paginated JSON rows from a completed job result file.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobIdPathParam"
          },
          {
            "$ref": "#/components/parameters/PageParam"
          },
          {
            "$ref": "#/components/parameters/LimitParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Results page fetched successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResultsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/JobNotCompleted"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "operationId": "getJobResults"
      }
    }
  },
  "components": {
    "parameters": {
      "JobIdPathParam": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "description": "Job id returned by POST /api/v1/jobs.",
        "schema": {
          "type": "string"
        }
      },
      "PageParam": {
        "name": "page",
        "in": "query",
        "description": "Offset pagination page number.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "LimitParam": {
        "name": "limit",
        "in": "query",
        "description": "Page size for results rows.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request data (validation, pagination, or malformed JSON).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "validation": {
                "summary": "Conditional field validation error",
                "value": {
                  "error": "params.usernames is required for followers/likers email source"
                }
              },
              "noAddonSelected": {
                "summary": "No add-on selected",
                "value": {
                  "error": "Select at least one add-on to continue.",
                  "error_code": "NO_ADDON_SELECTED"
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "missingApiKey": {
                "summary": "Missing API key",
                "value": {
                  "error": "Unauthorized"
                }
              }
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "The account does not have enough credits for this job.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "notEnoughCredits": {
                "summary": "Insufficient credits",
                "value": {
                  "error": "Insufficient credits. Required 500, available 120."
                }
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Request is forbidden (for example blocked subuser or phone-gated owner).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "blockedSubuser": {
                "summary": "Blocked subuser",
                "value": {
                  "error": "Subuser is blocked for this namespace."
                }
              },
              "phoneVerificationRequired": {
                "summary": "Phone verification required",
                "value": {
                  "error": "Phone verification required",
                  "requiresPhoneVerification": true
                }
              }
            }
          }
        }
      },
      "SubmitConflict": {
        "description": "Conflict on submission (for example idempotency mismatch or conflicting in-flight job).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "idempotencyConflict": {
                "summary": "Idempotency mismatch",
                "value": {
                  "error": "Idempotency-Key already used with a different payload"
                }
              }
            }
          }
        }
      },
      "JobNotCompleted": {
        "description": "Job exists but results are not currently available (not completed or enrichment upgrade in progress).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "stillProcessing": {
                "summary": "Job not completed",
                "value": {
                  "error": "Job is not completed (current status: PROCESSING)"
                }
              },
              "enrichmentInProgress": {
                "summary": "Enrichment in progress",
                "value": {
                  "error": "Job results are temporarily unavailable while enrichment is running"
                }
              }
            }
          }
        }
      },
      "JobEnrichmentConflict": {
        "description": "Conflict while starting retroactive enrichment.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/UpgradeConflictResponse"
            },
            "examples": {
              "sourceNotCompleted": {
                "summary": "Source not completed",
                "value": {
                  "error": "Job is not completed (current status: PROCESSING)",
                  "error_code": "SOURCE_NOT_COMPLETED"
                }
              },
              "upgradeAlreadyRunning": {
                "summary": "Upgrade already running",
                "value": {
                  "error": "Enrichment upgrade is already running for this job",
                  "error_code": "UPGRADE_ALREADY_RUNNING"
                }
              }
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Request JSON exceeds endpoint size limit.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Content-Type must be application/json.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Request throttled or rate-limited. Retry-After is returned for throttle/rate-limit responses.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "throttled": {
                "summary": "Rate limited",
                "value": {
                  "error": "Rate limit exceeded"
                }
              },
              "failedAuthThrottled": {
                "summary": "Failed-auth throttle",
                "value": {
                  "error": "Unauthorized"
                }
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Temporary service unavailability.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "examples": {
              "dependencyUnavailable": {
                "summary": "Temporary outage",
                "value": {
                  "error": "Service temporarily unavailable"
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Requested job was not found in your namespace.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Unexpected internal server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "JobSubmitRequest": {
        "oneOf": [
          {
            "type": "object",
            "description": "Followers extraction job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "followers",
                "type": "string"
              },
              "params": {
                "type": "object",
                "properties": {
                  "usernames": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30,
                      "pattern": "^[a-zA-Z0-9._]+$"
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "max_followers": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000,
                    "default": 50
                  },
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional profile enrichment add-on on delivered rows. Base profile_pic_url is already included without this add-on, and the add-on appends fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "required": [
                  "usernames"
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Following extraction job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "following",
                "type": "string"
              },
              "params": {
                "type": "object",
                "properties": {
                  "usernames": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30,
                      "pattern": "^[a-zA-Z0-9._]+$"
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "max_following": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000,
                    "default": 50
                  },
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional profile enrichment add-on on delivered rows. Base profile_pic_url is already included without this add-on, and the add-on appends fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "required": [
                  "usernames"
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Likers extraction job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "likers",
                "type": "string"
              },
              "params": {
                "type": "object",
                "properties": {
                  "usernames": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30,
                      "pattern": "^[a-zA-Z0-9._]+$"
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "max_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 12
                  },
                  "max_new_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12,
                    "default": 2
                  },
                  "max_total_likers": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000,
                    "default": 0
                  },
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional profile enrichment add-on on delivered rows. Base profile_pic_url is already included without this add-on, and the add-on appends fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "required": [
                  "usernames"
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Email extraction job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "emails",
                "type": "string"
              },
              "params": {
                "type": "object",
                "description": "Select source strategy for email extraction. Conditional required fields are defined in oneOf variants below. For emails, params.enrich_profiles=true is rejected and params.enrich_profiles=false is ignored for backward compatibility.",
                "properties": {
                  "email_source_type": {
                    "type": "string",
                    "enum": [
                      "followers",
                      "likers",
                      "hashtag_posters"
                    ],
                    "default": "followers",
                    "description": "Select email source strategy."
                  },
                  "usernames": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30,
                      "pattern": "^[a-zA-Z0-9._]+$"
                    },
                    "maxItems": 10,
                    "default": [],
                    "description": "Used when email_source_type is followers or likers."
                  },
                  "hashtag": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-zA-Z0-9_]+$",
                    "description": "Used when email_source_type=hashtag_posters."
                  },
                  "max_emails": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "default": 1000
                  },
                  "max_followers": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000,
                    "default": 50
                  },
                  "max_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 12
                  },
                  "max_new_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12,
                    "default": 2
                  },
                  "max_total_likers": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000,
                    "default": 0
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "oneOf": [
                  {
                    "title": "Hashtag posters source",
                    "description": "Use email_source_type=hashtag_posters. params.hashtag is required.",
                    "type": "object",
                    "properties": {
                      "email_source_type": {
                        "type": "string",
                        "const": "hashtag_posters",
                        "description": "Select email source strategy."
                      },
                      "hashtag": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 50,
                        "pattern": "^[a-zA-Z0-9_]+$",
                        "description": "Required when email_source_type=hashtag_posters."
                      }
                    },
                    "required": [
                      "email_source_type",
                      "hashtag"
                    ]
                  },
                  {
                    "title": "Followers or likers source",
                    "description": "Use email_source_type=followers or likers. params.usernames must include at least one username.",
                    "type": "object",
                    "properties": {
                      "email_source_type": {
                        "type": "string",
                        "enum": [
                          "followers",
                          "likers"
                        ],
                        "default": "followers",
                        "description": "Select email source strategy."
                      },
                      "usernames": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 30,
                          "pattern": "^[a-zA-Z0-9._]+$"
                        },
                        "maxItems": 10,
                        "minItems": 1,
                        "description": "Required when email_source_type is followers or likers."
                      }
                    },
                    "required": [
                      "usernames"
                    ]
                  }
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Phone extraction job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "phones",
                "type": "string"
              },
              "params": {
                "type": "object",
                "description": "Select source strategy for phone extraction. Conditional required fields are defined in oneOf variants below. For phones, params.enrich_profiles=true is rejected and params.enrich_profiles=false is ignored for backward compatibility.",
                "properties": {
                  "phone_source_type": {
                    "type": "string",
                    "enum": [
                      "followers",
                      "likers",
                      "hashtag_posters"
                    ],
                    "default": "followers",
                    "description": "Select phone source strategy."
                  },
                  "usernames": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30,
                      "pattern": "^[a-zA-Z0-9._]+$"
                    },
                    "maxItems": 10,
                    "default": [],
                    "description": "Used when phone_source_type is followers or likers."
                  },
                  "hashtag": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-zA-Z0-9_]+$",
                    "description": "Used when phone_source_type=hashtag_posters."
                  },
                  "max_phones": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "default": 1000
                  },
                  "max_followers": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000,
                    "default": 50
                  },
                  "max_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 12
                  },
                  "max_new_posts": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12,
                    "default": 2
                  },
                  "max_total_likers": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000,
                    "default": 0
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "oneOf": [
                  {
                    "title": "Hashtag posters source",
                    "description": "Use phone_source_type=hashtag_posters. params.hashtag is required.",
                    "type": "object",
                    "properties": {
                      "phone_source_type": {
                        "type": "string",
                        "const": "hashtag_posters",
                        "description": "Select phone source strategy."
                      },
                      "hashtag": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 50,
                        "pattern": "^[a-zA-Z0-9_]+$",
                        "description": "Required when phone_source_type=hashtag_posters."
                      }
                    },
                    "required": [
                      "phone_source_type",
                      "hashtag"
                    ]
                  },
                  {
                    "title": "Followers or likers source",
                    "description": "Use phone_source_type=followers or likers. params.usernames must include at least one username.",
                    "type": "object",
                    "properties": {
                      "phone_source_type": {
                        "type": "string",
                        "enum": [
                          "followers",
                          "likers"
                        ],
                        "default": "followers",
                        "description": "Select phone source strategy."
                      },
                      "usernames": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 30,
                          "pattern": "^[a-zA-Z0-9._]+$"
                        },
                        "maxItems": 10,
                        "minItems": 1,
                        "description": "Required when phone_source_type is followers or likers."
                      }
                    },
                    "required": [
                      "usernames"
                    ]
                  }
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Hashtag research job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "hashtag",
                "type": "string"
              },
              "params": {
                "type": "object",
                "properties": {
                  "hashtag": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "pattern": "^[a-zA-Z0-9_]+$"
                  },
                  "hashtag_content_type": {
                    "type": "string",
                    "enum": [
                      "recent",
                      "top"
                    ],
                    "default": "recent",
                    "description": "Choose which hashtag feed to scan. Defaults to recent."
                  },
                  "hashtag_scrape_type": {
                    "type": "string",
                    "enum": [
                      "posters",
                      "likers"
                    ],
                    "default": "posters",
                    "description": "Choose whether to return posters or likers. Defaults to posters."
                  },
                  "max_results": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "default": 100
                  },
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional profile enrichment add-on on delivered rows. Base profile_pic_url is already included without this add-on, and the add-on appends fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "required": [
                  "hashtag"
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Location research job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "location",
                "type": "string"
              },
              "params": {
                "type": "object",
                "properties": {
                  "location_query": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "location_id": {
                    "type": "string",
                    "pattern": "^\\d+$"
                  },
                  "location_content_type": {
                    "type": "string",
                    "enum": [
                      "recent",
                      "top"
                    ],
                    "default": "recent",
                    "description": "Choose which location feed to scan. Defaults to recent."
                  },
                  "location_scrape_type": {
                    "type": "string",
                    "enum": [
                      "posters",
                      "likers"
                    ],
                    "default": "posters",
                    "description": "Choose whether to return posters or likers. Defaults to posters."
                  },
                  "max_results": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "default": 100
                  },
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional profile enrichment add-on on delivered rows. Base profile_pic_url is already included without this add-on, and the add-on appends fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "required": [
                  "location_id"
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          },
          {
            "type": "object",
            "description": "Single post likers job.",
            "properties": {
              "external_user_id": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "pattern": "^[^\\u0000-\\u001F\\u007F]+$",
                "description": "Your stable customer/user ID from your system (for example: customer-123). Control characters are not allowed."
              },
              "tool_type": {
                "const": "single-post-likers",
                "type": "string"
              },
              "params": {
                "type": "object",
                "properties": {
                  "post_url": {
                    "type": "string",
                    "minLength": 1
                  },
                  "max_results": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "default": 100
                  },
                  "enrich_profiles": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional profile enrichment add-on on delivered rows. Base profile_pic_url is already included without this add-on, and the add-on appends fields such as biography, email, phone, website, and business."
                  },
                  "enrich_country_date": {
                    "type": "boolean",
                    "default": false,
                    "description": "Enable optional country/date add-on on delivered rows. date is normalized to MM-YYYY (or empty when unparseable)."
                  }
                },
                "required": [
                  "post_url"
                ]
              }
            },
            "required": [
              "external_user_id",
              "tool_type",
              "params"
            ]
          }
        ],
        "discriminator": {
          "propertyName": "tool_type"
        },
        "description": "Request payload for creating a job. Select a variant by tool_type."
      },
      "JobSubmitResponse": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED",
              "PROCESSING",
              "COMPLETED",
              "FAILED",
              "CANCELLED"
            ]
          },
          "replayed": {
            "type": "boolean"
          }
        },
        "required": [
          "job_id",
          "status",
          "replayed"
        ]
      },
      "UpgradeConflictResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_code": {
            "type": "string",
            "enum": [
              "SOURCE_NOT_COMPLETED",
              "UPGRADE_ALREADY_RUNNING",
              "SOURCE_ALREADY_ENRICHED",
              "SOURCE_ARTIFACT_UNAVAILABLE"
            ]
          }
        },
        "required": [
          "error",
          "error_code"
        ],
        "additionalProperties": true
      },
      "UpgradePreflightResponse": {
        "type": "object",
        "properties": {
          "eligible": {
            "type": "boolean"
          },
          "enriched": {
            "type": "boolean"
          },
          "fully_enriched": {
            "type": "boolean"
          },
          "supported_addons": {
            "type": "object",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "applied_addons": {
            "type": "object",
            "description": "Add-ons already materialized in the current output.",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "selected_addons": {
            "type": "object",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "upgrade_status": {
            "type": "string",
            "enum": [
              "idle",
              "queued",
              "processing",
              "completed",
              "failed"
            ]
          },
          "upgrade_last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason_code": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "NO_ADDON_SELECTED",
              "SOURCE_NOT_FOUND",
              "SOURCE_FORBIDDEN",
              "SOURCE_NOT_COMPLETED",
              "SOURCE_ALREADY_ENRICHED",
              "UPGRADE_ALREADY_RUNNING",
              "SOURCE_TYPE_NOT_SUPPORTED",
              "INTERNAL_SOURCE_NOT_SUPPORTED",
              "ADDON_NOT_SUPPORTED_FOR_SOURCE",
              "SOURCE_ARTIFACT_UNAVAILABLE",
              "INSUFFICIENT_CREDITS",
              null
            ]
          },
          "reason_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "input_rows": {
            "type": "integer",
            "minimum": 0
          },
          "credits_per_row_profiles": {
            "type": "integer",
            "minimum": 0
          },
          "credits_per_row_country_date": {
            "type": "integer",
            "minimum": 0
          },
          "credits_per_row": {
            "type": "integer",
            "minimum": 0
          },
          "reserved_credits": {
            "type": "integer",
            "minimum": 0
          },
          "available_credits": {
            "type": "integer",
            "minimum": 0
          },
          "shortfall_credits": {
            "type": "integer",
            "minimum": 0
          },
          "fields_added": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Columns that would actually be added by this request. Base fields like profile_pic_url are never included, while business and website are included when profile enrichment is selected."
          }
        },
        "required": [
          "eligible",
          "enriched",
          "fully_enriched",
          "supported_addons",
          "applied_addons",
          "selected_addons",
          "upgrade_status",
          "upgrade_last_error",
          "reason_code",
          "reason_message",
          "input_rows",
          "credits_per_row_profiles",
          "credits_per_row_country_date",
          "credits_per_row",
          "reserved_credits",
          "available_credits",
          "shortfall_credits",
          "fields_added"
        ],
        "additionalProperties": true
      },
      "UpgradeStartResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "replayed": {
            "type": "boolean"
          },
          "source_job_id": {
            "type": "string"
          },
          "upgrade_job_id": {
            "type": "string"
          },
          "upgrade_status": {
            "type": "string",
            "enum": [
              "queued",
              "processing",
              "completed"
            ]
          },
          "upgrade_last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "enriched": {
            "type": "boolean"
          },
          "fully_enriched": {
            "type": "boolean"
          },
          "supported_addons": {
            "type": "object",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "applied_addons": {
            "type": "object",
            "description": "Add-ons already materialized in the current output.",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "selected_addons": {
            "type": "object",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "input_rows": {
            "type": "integer",
            "minimum": 0
          },
          "credits_per_row_profiles": {
            "type": "integer",
            "minimum": 0
          },
          "credits_per_row_country_date": {
            "type": "integer",
            "minimum": 0
          },
          "credits_per_row": {
            "type": "integer",
            "minimum": 0
          },
          "reserved_credits": {
            "type": "integer",
            "minimum": 0
          },
          "fields_added": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Columns actually added by this request. Base fields like profile_pic_url are never included, while business and website are included when profile enrichment is selected."
          }
        },
        "required": [
          "success",
          "source_job_id",
          "upgrade_status",
          "upgrade_last_error",
          "enriched",
          "fully_enriched",
          "supported_addons",
          "applied_addons",
          "selected_addons",
          "input_rows",
          "credits_per_row_profiles",
          "credits_per_row_country_date",
          "credits_per_row",
          "reserved_credits",
          "fields_added"
        ],
        "additionalProperties": true
      },
      "JobListItem": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string"
          },
          "external_user_id": {
            "type": "string"
          },
          "namespace": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED",
              "PROCESSING",
              "COMPLETED",
              "FAILED",
              "CANCELLED"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "progress_percent": {
            "type": "number"
          },
          "number_of_results": {
            "type": "integer"
          },
          "cost_charged": {
            "type": "number"
          },
          "tool_type": {
            "type": "string",
            "enum": [
              "followers",
              "following",
              "likers",
              "emails",
              "phones",
              "hashtag",
              "location",
              "single-post-likers",
              "unknown"
            ]
          },
          "enriched": {
            "type": "boolean"
          },
          "fully_enriched": {
            "type": "boolean"
          },
          "supported_addons": {
            "type": "object",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "applied_addons": {
            "type": "object",
            "description": "Add-ons already materialized in the current output.",
            "properties": {
              "enrich_profiles": {
                "type": "boolean"
              },
              "enrich_country_date": {
                "type": "boolean"
              }
            },
            "required": [
              "enrich_profiles",
              "enrich_country_date"
            ],
            "additionalProperties": false
          },
          "enrichment_requested": {
            "type": "boolean",
            "description": "True when enrichment was requested or already applied. Submit-time add-ons remain requested until the job output is completed."
          },
          "enrichment_completed": {
            "type": "boolean"
          },
          "upgrade_status": {
            "type": "string",
            "enum": [
              "idle",
              "queued",
              "processing",
              "completed",
              "failed"
            ]
          },
          "upgrade_last_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "job_id",
          "external_user_id",
          "namespace",
          "status",
          "created_at",
          "updated_at",
          "completed_at",
          "progress_percent",
          "number_of_results",
          "cost_charged",
          "tool_type",
          "enriched",
          "fully_enriched",
          "supported_addons",
          "applied_addons",
          "enrichment_requested",
          "enrichment_completed",
          "upgrade_status",
          "upgrade_last_error"
        ],
        "additionalProperties": true
      },
      "JobResultsResponse": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string"
          },
          "external_user_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "QUEUED",
              "PROCESSING",
              "COMPLETED",
              "FAILED",
              "CANCELLED"
            ]
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/OffsetPagination"
          }
        },
        "required": [
          "job_id",
          "external_user_id",
          "status",
          "columns",
          "data",
          "messages",
          "pagination"
        ],
        "additionalProperties": true
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "OffsetPagination": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "has_next": {
            "type": "boolean"
          },
          "has_prev": {
            "type": "boolean"
          }
        },
        "required": [
          "page",
          "limit",
          "total",
          "total_pages",
          "has_next",
          "has_prev"
        ]
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "string"
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bearer API key authentication."
      }
    }
  }
}
