Saltar a contenido

API

Info

Ostorlab ha añadido soporte para la codificación Universal Binary JSON. UBJSON proporciona varios beneficios de rendimiento y añade soporte para tipos de datos binarios. Para obtener más información sobre el formato y las bibliotecas disponibles, consulte el siguiente enlace UBJSON Spec. El soporte para JSON sigue estando disponible y cualquiera de los dos puede especificarse utilizando el encabezado content type.

En esta guía, le mostraremos los pasos para utilizar la API de Ostorlab. Mediante la API, usted puede realizar todo tipo de tareas, desde la creación de un escaneo y el acceso al progreso de los escaneos, hasta la enumeración de vulnerabilidades. La siguiente sección describirá cómo utilizar la API, así como la forma de experimentarla y probarla.

Acceso

Existen múltiples formas de acceder a la API. Usted puede utilizar la aplicación web GraphiQL para experimentar con la API, o bien emplear scripts, ya que su uso habitual está destinado a la automatización en pipelines de despliegue o a la automatización de la creación y monitorización de un gran número de escaneos.

Access

1. Sandbox

GraphiQL es accesible a través de la siguiente URL https://api.ostorlab.co/apis/graphql

Sandbox

La API es accesible únicamente para usuarios autenticados. Asegúrese de haberse autenticado en https://api.ostorlab.co/portal/login

auth_page

Ejemplos

Para ofrecerle una idea sobre cómo utilizar la API, aquí tiene algunos ejemplos de consultas comunes:

Enumerar Escaneos

Para enumerar todos los escaneos que pertenecen a la organización del usuario actual:

Para ejecutar la consulta, haga clic en el botón de ejecución.

En la sección derecha, usted puede ver el resultado de la consulta ejecutada.

Enumerar Escaneos por Filtro

Para enumerar los escaneos de una aplicación móvil en particular, utilice el argumento target para especificar el nombre del paquete de la aplicación o el ID del bundle.

La API de escaneos soporta otros filtros como targetAssetTypes, riskRatings, progress y puede ser ordenada y clasificada utilizando los argumentos orderBy y sort.

Detalles del Escaneo

Usted también puede recuperar los detalles de un único escaneo utilizando su ID de la siguiente manera:

Detalles de Vulnerabilidad

Además, esta consulta le permite recuperar la lista y los detalles de las vulnerabilidades de un escaneo:

Progreso del Escaneo

Además de eso, para determinar la etapa actual de progreso del escaneo, usted puede utilizar la siguiente consulta:

Crear Nuevo Escaneo

Para crear un escaneo, los archivos se suben mediante una solicitud HTTP multiparte.

mutation newMobileScan($title: String!, $assetType: String!, $application: Upload!, $scanProfile: String!) {
      createMobileScan(title: $title, assetType:$assetType, application: $application, scanProfile: $scanProfile) {
        scan {
            id
        }
      }
    }

Crear un nuevo escaneo con autenticación

La creación de un escaneo con credenciales es un proceso de dos pasos. Primero, las credenciales se añaden al almacén de credenciales y se devuelve un ID de credencial; luego, se envía una solicitud de creación de escaneo especificando los IDs de las credenciales a utilizar en dicho escaneo.

Por ejemplo, para crear una credencial sencilla de inicio de sesión con contraseña. Usted puede usar lo siguiente:

mutation newTestCredential {
  createTestCredentials(testCredentials: {loginPassword: {login: "login1", password: "password1"}}) {
    testCredentials {
      __typename
      ... on LoginPasswordTestCredentials {
        id
      }
    }
  }
}

Lo cual devolverá el ID de la siguiente manera:

{
  "data": {
    "createTestCredentials": {
      "testCredentials": {
        "__typename": "LoginPasswordTestCredentials",
        "id": "901"
      }
    }
  }
}

Para pasar el ID de la credencial, usted debe añadir el argumento credentialIds con la lista de IDs:

mutation newMobileScan($title: String!, $assetType: String!, $application: Upload!, $scanProfile: String!) {
  createMobileScan(title: $title, assetType: $assetType, application: $application, scanProfile: $scanProfile, credentialIds: [901]) {
    scan {
      id
    }
  }
}

Activos Descubiertos

Usted puede recuperar la lista de activos descubiertos (Dominios, Aplicaciones Móviles, IPs...) utilizando la siguiente consulta. Esto permite explorar los activos que han sido identificados por la plataforma de descubrimiento de la superficie de ataque. La consulta soporta filtros como el tipo de activo, la propiedad y la profundidad de los nodos relacionados.

query NodesEdges($depth: Int, $limit: Int, $filterExcluded: Boolean, $assets: [NGAssetIdInputType], $ownerIds: [Int], $nodeKeys: [String], $nodeTypes: [String]) {
  nodesEdges(
    depth: $depth
    limit: $limit
    filterExcluded: $filterExcluded
    assets: $assets
    ownerIds: $ownerIds
    nodeKeys: $nodeKeys
    nodeTypes: $nodeTypes
  ) {
    nodes {
      type
      assetType
      assetId
      key
      ownershipType
      customColor
      attributes {
        name
        value
      }
    }
    edges {
      fromType
      fromKey
      toType
      toKey
      attributes {
        name
        value
      }
    }
  }
}

Variables de Ejemplo:

{
  "depth": 1,
  "limit": 10,
  "ownerIds": [],
  "nodeKeys": [],
  "nodeTypes": [
    "androidApp"
  ]
}

2. Scripts

Como se mencionó anteriormente, GraphiQL no es la única forma de acceder a la API, ya que usted también puede utilizarla a través de scripts. Su uso típico es para la automatización en pipelines de despliegue o para automatizar la creación y monitorización de un gran número de escaneos. Para el proceso de autenticación, usted dispone de dos opciones: Autenticación basada en token y autenticación por API Key.

Autenticación basada en token

El token se puede recuperar desde la siguiente URL https://api.ostorlab.co/apis/token enviando el nombre de usuario y la contraseña. Se creará un token vinculado al usuario. A continuación, el token se establece en un encabezado de Autorización (Authorization) con el valor Token {value} para autenticar solicitudes posteriores a https://api.ostorlab.co/apis/graphql_token.

Autenticación por API Key

La API Key puede recuperarse desde su panel de control https://report.ostorlab.co. Usted debe hacer clic en Integrations/API:

Luego en el menú de API keys.

Haga clic en el botón nuevo para generar una nueva clave

Copie la API Key (usted puede añadir un nombre y una fecha de caducidad a su clave), y finalmente no olvide hacer clic en el botón de guardar para conservar su clave.

La API Key se utiliza como el valor del encabezado con la clave X-Api-Key para autenticar solicitudes posteriores a https://api.ostorlab.co/apis/graphql_token.

Crear un escaneo
import json
import requests

query = '''
mutation MobileScan($title: String!, $assetType: String!, $application: Upload!, $sboms: [Upload], $scanProfile: String!, $credentialIds: [Int]) {
  createMobileScan(title: $title, assetType: $assetType, application: $application, sboms: $sboms, scanProfile: $scanProfile, credentialIds: $credentialIds) {
    scan {
      id
    }
  }
}
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}
# Variables
title = "scan_title"
asset_type = "android"
scan_profile = "Fast Scan"
data = {"operations": json.dumps({"query":query,"variables": {"title":title,"assetType":asset_type,"application": None,"scanProfile": scan_profile,},}), "map": json.dumps({"0": ["variables.application"]}),}
path_to_file = "my_full_path"
# Enviar solicitud post de la consulta.
with open(path_to_file, "rb") as f:
    request = requests.post(url='https://api.ostorlab.co/apis/graphql_token/',
                          data=data,
                          files={"0": f.read()},  
                          headers=headers)
    print(request.json())
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header "X-Api-Key: $API_KEY" \
-F 'operations={"query": "mutation newMobileScan($title: String!, $assetType: String!, \
$application: Upload!, $scanProfile: String!) { createMobileScan(title: $title, assetType:$assetType, \
application: $application, scanProfile: $scanProfile) {scan {id }  }}", "variables": \
{"title": "test_title", "assetType": "android", "application": null, "scanProfile": "$SCAN_PROFILE"}}' \
-F 'map={"0": ["variables.application"]}' -F "0=@$PATH_APPLICATION"
Extraer el progreso del escaneo
import requests

query = '''
query {
  scans {
    scans {   
      id
      title
      progress
    }
  }
}
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}
# Enviar solicitud post de la consulta.
request = requests.post(url='https://api.ostorlab.co/apis/graphql_token/',
                      json={"query": query},
                      headers=headers)
print(request.json())
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header 'Content-Type: application/json' \
--header "X-Api-Key: $API_KEY" \
--data '{"query": "query {scans { scans { id title  progress }}}"}'  
Extraer los tickets del escaneo
import requests

query = '''
query tickets($scanId: Int) {
    tickets(scanId: $scanId) {   
    tickets{
      id  
      withinSlo
      status
      priority  
      }
    }
  }
'''

# Variables
scan_id = XXXXX
# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}
# Enviar solicitud post de la consulta.
request = requests.post(url='https://api.ostorlab.co/apis/graphql_token/',
                      json={"query": query, "variables": {"scanId": scan_id}},
                      headers=headers)
print(request.json())
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header 'Content-Type: application/json' \
--header "X-Api-Key: $API_KEY" \
--data '{"query": "query tickets($scanId: Int) { tickets(scanId: $scanId) { tickets { id withinSlo status priority } } }", "variables": {"scanId": $SCAN_ID}}'  
Crear un escaneo with SBOM
import json
import requests
import pathlib

query = '''
mutation MobileScan($title: String!, $assetType: String!, $application: Upload!, $sboms: [Upload], $scanProfile: String!) {
  createMobileScan(title: $title, assetType: $assetType, application: $application, sboms: $sboms, scanProfile: $scanProfile) {
    scan {
      id
    }
  }
}
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}

# Variables
title = "scan_with_sbom"
asset_type = "android"
scan_profile = "Fast Scan"
path_to_apk = "your_apk.apk"
path_to_sboms = ["gradle.lockfile"]

# Datos
data = {
    "operations": json.dumps({
        "query": query,
        "variables": {
            "title": title,
            "assetType": asset_type,
            "application": None,
            "sboms": [None],  # Solo un elemento
            "scanProfile": scan_profile
        }
    }),
    "map": json.dumps({
        "0": ["variables.application"],
        "1": ["variables.sboms.0"]
    })
}

# Enviar solicitud post de la consulta
apk_content = pathlib.Path(path_to_apk).read_bytes()
sbom_content = pathlib.Path(path_to_sboms[0]).read_bytes()
response = requests.post(
    url='https://api.ostorlab.co/apis/graphql_token/',
    data=data,
    files={
        "0": apk_content,
        "1": sbom_content
    },
    headers=headers
)
print(response.json())
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header "X-Api-Key: $API_KEY" \
-F 'operations={"query": "mutation MobileScan($title: String!, $assetType: String!, $application: Upload!, $sboms: [Upload], $scanProfile: String!) { createMobileScan(title: $title, assetType: $assetType, application: $application, sboms: $sboms, scanProfile: $scanProfile) { scan { id } } }", "variables": {"title": "scan_with_sbom", "assetType": "android", "application": null, "sboms": [null], "scanProfile": "Fast Scan"}}' \
-F 'map={"0": ["variables.application"], "1": ["variables.sboms.0"]}' \
-F "0=@$PATH_APK" \
-F "1=@$PATH_SBOM"
Crear un escaneo with test credentials

Crear las credenciales de prueba

Ejemplo de creación de una credencial sencilla de inicio de sesión con contraseña:

import json
import requests

query = '''
    mutation CreateTestCredentials($credentials: TestCredentialsInput!) {
          createTestCredentials(testCredentials: $credentials) {
            testCredentials {
              ... on LoginPasswordTestCredentials {
                id
                credentialName
                login
                password
                type
                url
              }
            }
          }
        }
'''


# Configurar clave API en el encabezado Authorization
api_key = "XXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}

# Variables
variables = {
    "credentials": {
        "credentialName": "login_creds",
        "loginPassword": {
            "login": "fake_login",
            "password": "fake_password",
            "url": "fake_url"
        },
    }
}

# Datos
data = {
    "query": query,
    "variables": variables
}

# Enviar solicitud post de la consulta
response = requests.post(
    url='https://api.ostorlab.co/apis/graphql_token/',
    json=data,
    headers=headers
)
print(response.json())
curl -X POST https://api.ostorlab.co/apis/graphql_token/ -H "X-Api-Key: $API_KEY" -H "Content-Type: application/json" -d '{
  "query": "mutation CreateTestCredentials($credentials: TestCredentialsInput!) { createTestCredentials(testCredentials: $credentials) { testCredentials { ... on LoginPasswordTestCredentials { id credentialName login password type url } } } }",
  "variables": {
    "credentials": {
      "credentialName": "login_creds",
      "loginPassword": {
        "login": "fake_login",
        "password": "fake_password",
        "url": "fake_url"
      }
    }
  }
}'

Ejemplo de creación de una credencial de prueba personalizada:

import json
import requests

query = '''
    mutation CreateTestCredentials($credentials: TestCredentialsInput!) {
      createTestCredentials(testCredentials: $credentials) {
        testCredentials {
          ... on CustomTestCredentials {
            id
            credentialName
            credentials {
              name
              value
            }
          }
        }
      }
    }
'''


# Configurar clave API en el encabezado Authorization
api_key = "XXXXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}

# Variables
variables = {
        "credentials": {
            "credentialName": "testCustomCredentials",
            "custom": {
                "credentials": [
                    {"name": "a", "value": "b"},
                    {"name": "a2", "value": "b2"},
                ],
            },
        }
    }

# Datos
data = {
    "query": query,
    "variables": variables
}

# Enviar solicitud post de la consulta
response = requests.post(
    url='https://api.ostorlab.co/apis/graphql_token/',
    json=data,
    headers=headers
)
print(response.json())
curl -X POST https://api.ostorlab.co/apis/graphql_token/ \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "query": "mutation CreateTestCredentials($credentials: TestCredentialsInput!) { createTestCredentials(testCredentials: $credentials) { testCredentials { ... on CustomTestCredentials { id credentialName credentials { name value } } } } }",
  "variables": {
    "credentials": {
      "credentialName": "testCustomCredentials",
      "custom": {
        "credentials": [
          { "name": "a", "value": "b" },
          { "name": "a2", "value": "b2" }
        ]
      }
    }
  }
}'

Consultar las credenciales de prueba

import requests

query = '''
    query {
      testsCredentials(page: 1, numberElements: 10) {
        pageInfo {
          count
          numPages
        }
        testsCredentials {
          __typename
          ... on LoginPasswordTestCredentials {
            credentialName
            id
            login
            password
            role
            url
          }
          ... on CreditCardTestCredentials {
            id
            credentialName
            creditCardNumber
            expirationDate
            name
            cvv
          }
          ... on AddressTestCredentials {
            id
            credentialName
            addressLine
            city
            zipCode
            country
          }
          ... on EmailTestCredentials {
            id
            credentialName
            email
          }
          ... on PhoneNumberTestCredentials {
            id
            credentialName
            phoneNumber
          }
          ... on ScriptTestCredentials {
            id
            credentialName
            script
          }
          ... on TlsCertificateTestCredentials {
            id
            credentialName
            tlsCertificate
          }
          ... on CustomTestCredentials {
            id
            credentialName
            credentials {
              name
              value
            }
          }
          ... on TestHeaders {
            id
            credentialName
            testHeaders {
              name
              value
            }
          }
          ... on BasicTestCredentials {
            id
            credentialName
            login
            password
          }
        }
      }
    }
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}

# Enviar solicitud post de la consulta
response = requests.post(
    url='https://api.ostorlab.co/apis/graphql_token/',
    json={"query": query},
    headers=headers
)
print(response.json())
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header 'Content-Type: application/json' \
--header "X-Api-Key: $API_KEY" \
--data '{"query": "query { testsCredentials(page: 1, numberElements: 10) { pageInfo { count numPages } testsCredentials { __typename ... on LoginPasswordTestCredentials { credentialName id login password role url } ... on CreditCardTestCredentials { id credentialName creditCardNumber expirationDate name cvv } ... on AddressTestCredentials { id credentialName addressLine city zipCode country } ... on EmailTestCredentials { id credentialName email } ... on PhoneNumberTestCredentials { id credentialName phoneNumber } ... on ScriptTestCredentials { id credentialName script } ... on TlsCertificateTestCredentials { id credentialName tlsCertificate } ... on CustomTestCredentials { id credentialName credentials { name value } } ... on TestHeaders { id credentialName testHeaders { name value } } ... on BasicTestCredentials { id credentialName login password } } } }"}'

Create a scan with test credentials

import json
import requests

query = '''
mutation MobileScan($title: String!, $assetType: String!, $application: Upload!, $scanProfile: String!, $credentialIds: [Int]) {
  createMobileScan(title: $title, assetType: $assetType, application: $application, scanProfile: $scanProfile, credentialIds: $credentialIds) {
    scan {
      id
    }
  }
}
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}

# Variables
title = "scan_with_test_credentials"
asset_type = "android"
scan_profile = "Full Scan"
path_to_apk = "your_apk.apk"

# IDs de credenciales a utilizar en el escaneo, usted puede obtener el ID de la credencial desde el paso previo de creación de credenciales de prueba.
credential_ids = [123]

# Datos
data = {
    "operations": json.dumps({
        "query": query,
        "variables": {
            "title": title,
            "assetType": asset_type,
            "application": None,
            "scanProfile": scan_profile,
            "credentialIds": credential_ids
        }
    }),
    "map": json.dumps({
        "0": ["variables.application"]
    })
}

# Enviar solicitud post de la consulta
apk_content = pathlib.Path(path_to_apk).read_bytes()
response = requests.post(
    url='https://api.ostorlab.co/apis/graphql_token/',
    data=data,
    files={
        "0": apk_content
    },
    headers=headers
)
print(response.json())
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header "X-Api-Key: $API_KEY" \
-F 'operations={"query": "mutation MobileScan($title: String!, $assetType: String!, $application: Upload!, $scanProfile: String!, $credentialIds: [Int]) { createMobileScan(title: $title, assetType: $assetType, application: $application, scanProfile: $scanProfile, credentialIds: $credentialIds) { scan { id } } }", "variables": {"title": "scan_with_test_credentials", "assetType": "android", "application": null, "scanProfile": "Full Scan", "credentialIds": [123]}}' \
-F 'map={"0": ["variables.application"]}' \
-F "0=@$PATH_APK"
Crear un escaneo with on prem scanner
  • Paso 1: Recuperar Escáneres Disponibles

Antes de crear un escaneo, usted necesita identificar primero el ID del escáner. Puede recuperar la lista de escáneres disponibles para su organización utilizando la siguiente consulta:

import json
import requests

query_scanners = '''
query GetScanners {
  scanners {
    scanners {
      id
      uuid
      name
      description
    }
  }
}
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}
path_to_file = "my_full_path"
data = {"operations": json.dumps({"query": query_scanners}) , "map": json.dumps({}),}
# Enviar solicitud post de la consulta.
with open(path_to_file, "rb") as f:
    request = requests.post(url='https://api.ostorlab.co/apis/graphql_token/',
                          data=data,
                          files={"0": f.read()},
                          headers=headers)
    print(request.json())
curl -v -X POST "https://api.ostorlab.co/apis/graphql_token/" \
  -H "X-Api-Key: $API_KEY" \
  -F 'operations={
    "query": "query GetScanners { scanners { scanners { id uuid name description } } }"
  }' \
  -F 'map={}' \
  -F "0=@$PATH_TO_FILE"

Esta solicitud devolverá una lista de escáneres asociados con su cuenta. Tenga en cuenta el ID del escáner que desea utilizar al crear un escaneo.

  • Paso 2: Crear un Escaneo Web

Una vez que haya identificado el ID de escáner correcto, usted puede crear un nuevo escaneo web proporcionando el título del escaneo, las URLs objetivo, el perfil de escaneo y el ID del escáner.

import json
import requests

query = '''
mutation NewWebScan($title: String, $urls: [String]!, $scanProfile: String!,  $scannerId: Int,) {
  createWebScan(title: $title, urls: $urls, scanProfile: $scanProfile,  scannerId: $scannerId,) {
    scan {
      id
    }
  }
}
'''

# Configurar la API Key en el encabezado de autorización.
api_key = "XXXXXXXXXXX"
headers = {"X-Api-Key": f"{api_key}"}
# Variables
title = "scan_title"
urls = ["example.com"] # List of urls to scan.
scan_profile = "Full Web Scan"
scanner_id = 000000 # Replace with your scanner id.
path_to_file = "my_full_path"

data = {"operations": json.dumps({"query":query, "variables": {"title": title, "urls": urls,
                           "scanProfile": scan_profile, "scannerId": scanner_id
                          }}) , "map": json.dumps({}),}
# Enviar solicitud post de la consulta.
with open(path_to_file, "rb") as f:
    request = requests.post(url='https://api.ostorlab.co/apis/graphql_token/',
                          data=data,
                          files={"0": f.read()},
                          headers=headers)
    print(request.json())
curl -v -X POST "https://api.ostorlab.co/apis/graphql_token/" \
  -H "X-Api-Key: $API_KEY" \
  -F 'operations={
    "query": "mutation NewWebScan($title: String, $urls: [String]!, $scanProfile: String!, $scannerId: Int) { createWebScan(title: $title, urls: $urls, scanProfile: $scanProfile, scannerId: $scannerId) { scan { id } } }",
    "variables": {
      "title": "scan_title",
      "urls": ["example.com"],
      "scanProfile": "Full Web Scan",
      "scannerId": 000000
    }
  }' \
  -F 'map={}' \
  -F "0=@$PATH_TO_FILE"

Descargar PDF

Para descargar el informe en PDF, usted debe seguir 3 pasos:

1- Desencadenar la creación del Job de PDF. Este paso requiere un ID de escaneo y devuelve el Job Id.

2- Comprobar el estado del Job y esperar hasta que tenga el estado "SUCCESS".

3- Descargar el PDF utilizando el Job Id.

import json
import requests
import time
import sys

API_KEY = "XXXXXXXXXXX" # Reemplace con una API Key válida.
API_URL = 'https://api.ostorlab.co/apis/graphql_token/'
HEADERS = {"X-Api-Key": API_KEY}
PDF_OUTPUT_PATH = '/tmp/f123.pdf'
Time_SLEEP = 10

def generate_pdf_job(scan_id):
    """Iniciar la generación del PDF y devolver el ID del Job"""
    query = '''
    mutation generatePdf($scanId: Int!, $riskRatings: [RiskRating]!, $statuses: [TicketStatusEnum]!, $pdfReportTemplate: PDFReportTemplate!, $standards: [ScanCategoryGroupEnum]) {
        generatePdf(scanId: $scanId, riskRatings: $riskRatings, statuses: $statuses, pdfReportTemplate: $pdfReportTemplate, standards: $standards) {
          job {
            id
            status
          }
        }
    }
    '''

    variables = {
        "scanId": scan_id,
        "riskRatings": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "POTENTIALLY", "HARDENING"],
        "statuses": ["OPEN", "REOPEN"],
        "standards": [],
        "pdfReportTemplate": "full_report"
    }

    response = requests.post(API_URL, json={"query": query, "variables": variables}, headers=HEADERS)
    response_data = response.json()

    if 'errors' in response_data:
        print("Error durante la generación del PDF:", response_data, file=sys.stderr)
        sys.exit(1)

    return response_data['data']['generatePdf']['job']['id']

def wait_for_job_completion(job_id):
    """Esperar a que el Job se complete"""
    query = """
    query job($jobId: String!) {
        job(jobId: $jobId) {
            id
            status
        }
    }
    """

    while True:
        response = requests.post(API_URL, json={"query": query, "variables": {"jobId": job_id}}, headers=HEADERS)
        status = response.json()['data']['job']['status']

        if status == 'SUCCESS':
            break
        time.sleep(Time_SLEEP)

def download_pdf(job_id, output_path=PDF_OUTPUT_PATH):
    """Descargar el PDF generado"""
    response = requests.get(f'https://api.ostorlab.co/apis/job/result/{job_id}/', headers=HEADERS)
    with open(output_path, 'wb') as f:
        f.write(response.content)

def main(scan_id, output_path=PDF_OUTPUT_PATH):
    """Función principal para generar y descargar un PDF"""
    try:
        print("Iniciando la generación del PDF...")
        job_id = generate_pdf_job(scan_id)
        print(f"Job creado con ID: {job_id}")

        print("Esperando a que el Job se complete...")
        wait_for_job_completion(job_id)

        print("Descargando PDF...")
        download_pdf(job_id, output_path)
        print(f"PDF descargado exitosamente en: {output_path}")
    except Exception as e:
        print(f"Ocurrió un error: {str(e)}", file=sys.stderr)
        sys.exit(1)

scan_id = XXXXXXXXXXX # Reemplace con el ID del escaneo. 
main(scan_id)
curl -X POST https://api.ostorlab.co/apis/graphql_token/ -H "X-Api-Key: $API_KEY" -H "Content-Type: application/json" -d '{
  "query": "mutation generatePdf($scanId: Int!, $riskRatings: [RiskRating]!, $statuses: [TicketStatusEnum]!, $pdfReportTemplate: PDFReportTemplate!, $standards: [ScanCategoryGroupEnum]) { generatePdf(scanId: $scanId, riskRatings: $riskRatings, statuses: $statuses, pdfReportTemplate: $pdfReportTemplate, standards: $standards) { job { id status } } }",
  "variables": {
    "scanId": $SCAN_ID,
    "riskRatings": ["CRITICAL", "HIGH", "MEDIUM", "LOW", "POTENTIALLY", "HARDENING"],
    "statuses": ["OPEN", "REOPEN"],
    "standards": [],
    "pdfReportTemplate": "full_report"
  }
}'

Extraiga el estado del Job id y utilice la consulta a continuación para extraer el estado del Job (donde XXXXXXX es el ID del Job):

``` shell
curl -v -X POST https://api.ostorlab.co/apis/graphql \
--header 'Content-Type: application/json' \
--header "X-Api-Key: $API_KEY" \
--data '{"query": "query { job(jobId: XXXXXXX) { id status } }"}'
```

Una vez que el estado cambie a SUCCESS, usted puede ejecutar la siguiente consulta para descargar el PDF:

``` shell
curl --header "X-Api-Key: $API_KEY" "https://api.ostorlab.co/apis/job/result/<job_id>/" -o <output_path>
```

En resumen, esta guía fue un recorrido por la API de Ostorlab. Exploramos varios métodos para utilizar la API, incluyendo la aplicación web GraphiQL y los scripts. Adicionalmente, examinamos un conjunto de consultas comunes para que sirvan de referencia.