Prefere um cliente tipado e gerado? O documento OpenAPI é público:
/v1/vestibulares/openapi.json.
Ele alimenta qualquer gerador — openapi-typescript, openapi-generator,
hey-api — sem precisar de nada nosso.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 {
listExams: () => get("/v1/vestibulares/exams"),
listQuestions: (filtros?: {
examId?: string;
year?: number;
subjectId?: string;
difficulty?: "easy" | "medium" | "hard";
page?: number;
limit?: number;
}) => get("/v1/vestibulares/questions", filtros),
getQuestion: (id: string) => get(`/v1/vestibulares/questions/${id}`),
};
}
// uso
const client = enemhub(process.env.ENEMHUB_API_KEY!);
const { data: exames } = await client.listExams();
const { data, meta } = await client.listQuestions({ examId: exames[0].id, limit: 50 });
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_exams(self):
return self._get("/v1/vestibulares/exams")
def list_questions(self, exam_id=None, year=None, subject_id=None, difficulty=None, page=1, limit=20):
return self._get(
"/v1/vestibulares/questions",
examId=exam_id, year=year, subjectId=subject_id,
difficulty=difficulty, page=page, limit=limit,
)
def get_question(self, question_id):
return self._get(f"/v1/vestibulares/questions/{question_id}")
# uso
client = EnemHub()
exames = client.list_exams()["data"]
resposta = client.list_questions(exam_id=exames[0]["id"], limit=50)
print(resposta["meta"]["total"])
<?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 listExams(): array
{
return $this->get('/v1/vestibulares/exams');
}
public function listQuestions(array $filtros = []): array
{
return $this->get('/v1/vestibulares/questions', $filtros);
}
public function getQuestion(string $id): array
{
return $this->get("/v1/vestibulares/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'));
$exames = $client->listExams()['data'];
$resposta = $client->listQuestions(['examId' => $exames[0]['id'], 'limit' => 50]);
echo $resposta['meta']['total'];
export ENEMHUB_API_KEY=ehub_vest_...
# catálogo de exames
curl https://api.enemhub.com.br/v1/vestibulares/exams \
-H "X-API-Key: $ENEMHUB_API_KEY"
# listar questões de um exame
curl -G https://api.enemhub.com.br/v1/vestibulares/questions \
-H "X-API-Key: $ENEMHUB_API_KEY" \
-d examId=3b1e... \
-d difficulty=hard \
-d limit=50
# uma questão
curl https://api.enemhub.com.br/v1/vestibulares/questions/9f3c1a2e-... \
-H "X-API-Key: $ENEMHUB_API_KEY"
# ver os headers de rate limit
curl -i https://api.enemhub.com.br/v1/vestibulares/questions?limit=1 \
-H "X-API-Key: $ENEMHUB_API_KEY" | grep -i ratelimit
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.