> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enemhub.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# SDKs

> Não publicamos pacote. Copie o cliente da sua linguagem e siga.

Não existe pacote oficial da EnemHub em npm, PyPI ou Packagist — e isso é
deliberado. A API é REST com autenticação por header: um SDK seria uma camada a
mais para você atualizar, sem nada em troca.

Abaixo, um cliente completo por linguagem. São poucas linhas, copie para o seu
projeto e adapte.

<Tip>
  Prefere um cliente tipado e gerado? O documento OpenAPI é público:
  [`/v1/enem/openapi.json`](https://api.enemhub.com.br/v1/enem/openapi.json).
  Ele alimenta qualquer gerador — `openapi-typescript`, `openapi-generator`,
  `hey-api` — sem precisar de nada nosso.
</Tip>

<CodeGroup>
  ```ts TypeScript theme={null}
  const BASE = "https://api.enemhub.com.br";

  export class EnemHubError extends Error {
    constructor(message: string, readonly status: number) {
      super(message);
    }
  }

  export function enemhub(apiKey: string) {
    async function get<T>(caminho: string, params: Record<string, unknown> = {}): Promise<T> {
      const query = new URLSearchParams(
        Object.entries(params)
          .filter(([, v]) => v !== undefined && v !== null)
          .map(([k, v]) => [k, String(v)]),
      );

      const res = await fetch(`${BASE}${caminho}?${query}`, {
        headers: { "X-API-Key": apiKey },
      });

      if (!res.ok) {
        const corpo = await res.json().catch(() => ({} as any));
        throw new EnemHubError(corpo.message ?? corpo.error ?? `HTTP ${res.status}`, res.status);
      }

      return res.json() as Promise<T>;
    }

    return {
      listQuestions: (filtros?: {
        year?: number;
        subjectId?: string;
        difficulty?: "easy" | "medium" | "hard";
        page?: number;
        limit?: number;
      }) => get("/v1/enem/questions", filtros),

      getQuestion: (id: string) => get(`/v1/enem/questions/${id}`),
    };
  }

  // uso
  const client = enemhub(process.env.ENEMHUB_API_KEY!);
  const { data, meta } = await client.listQuestions({ year: 2023, limit: 50 });
  ```

  ```python Python theme={null}
  import os
  import requests

  BASE = "https://api.enemhub.com.br"


  class EnemHubError(Exception):
      def __init__(self, message, status):
          super().__init__(message)
          self.status = status


  class EnemHub:
      def __init__(self, api_key=None):
          self.session = requests.Session()
          self.session.headers["X-API-Key"] = api_key or os.environ["ENEMHUB_API_KEY"]

      def _get(self, caminho, **params):
          res = self.session.get(
              f"{BASE}{caminho}",
              params={k: v for k, v in params.items() if v is not None},
              timeout=30,
          )

          if not res.ok:
              corpo = res.json() if res.headers.get("content-type", "").startswith("application/json") else {}
              raise EnemHubError(
                  corpo.get("message") or corpo.get("error") or f"HTTP {res.status_code}",
                  res.status_code,
              )

          return res.json()

      def list_questions(self, year=None, subject_id=None, difficulty=None, page=1, limit=20):
          return self._get(
              "/v1/enem/questions",
              year=year, subjectId=subject_id, difficulty=difficulty, page=page, limit=limit,
          )

      def get_question(self, question_id):
          return self._get(f"/v1/enem/questions/{question_id}")


  # uso
  client = EnemHub()
  resposta = client.list_questions(year=2023, limit=50)
  print(resposta["meta"]["total"])
  ```

  ```php PHP theme={null}
  <?php

  final class EnemHubError extends RuntimeException {}

  final class EnemHub
  {
      private const BASE = 'https://api.enemhub.com.br';

      public function __construct(private string $apiKey) {}

      public function listQuestions(array $filtros = []): array
      {
          return $this->get('/v1/enem/questions', $filtros);
      }

      public function getQuestion(string $id): array
      {
          return $this->get("/v1/enem/questions/{$id}");
      }

      private function get(string $caminho, array $params = []): array
      {
          $url = self::BASE . $caminho . '?' . http_build_query(array_filter($params));

          $ch = curl_init($url);
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_HTTPHEADER     => ['X-API-Key: ' . $this->apiKey],
              CURLOPT_TIMEOUT        => 30,
          ]);

          $body   = curl_exec($ch);
          $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
          curl_close($ch);

          $json = json_decode($body ?: '{}', true) ?? [];

          if ($status < 200 || $status >= 300) {
              throw new EnemHubError($json['message'] ?? $json['error'] ?? "HTTP {$status}", $status);
          }

          return $json;
      }
  }

  // uso
  $client = new EnemHub(getenv('ENEMHUB_API_KEY'));
  $resposta = $client->listQuestions(['year' => 2023, 'limit' => 50]);
  echo $resposta['meta']['total'];
  ```

  ```bash cURL theme={null}
  export ENEMHUB_API_KEY=ehub_enem_...

  # listar
  curl -G https://api.enemhub.com.br/v1/enem/questions \
    -H "X-API-Key: $ENEMHUB_API_KEY" \
    -d year=2023 \
    -d difficulty=hard \
    -d limit=50

  # uma questão
  curl https://api.enemhub.com.br/v1/enem/questions/9f3c1a2e-... \
    -H "X-API-Key: $ENEMHUB_API_KEY"

  # ver os headers de rate limit
  curl -i https://api.enemhub.com.br/v1/enem/questions?limit=1 \
    -H "X-API-Key: $ENEMHUB_API_KEY" | grep -i ratelimit
  ```
</CodeGroup>

<Note>
  Os clientes acima não fazem retry. Se a sua carga é pesada o bastante para
  encostar no limite por minuto, junte o backoff de
  [Exemplos](/enem/exemplos#lidar-com-limites).
</Note>
