diff --git a/README.md b/README.md
index 8e2ceb7..46759ce 100644
--- a/README.md
+++ b/README.md
@@ -74,11 +74,11 @@ Detailed explanations:
## Current status
-The project foundation and **Phases 1–8 are complete**. **Phase 9: External Libraries is in progress** with two reviewed-track chapters: [`pandas`](external-libraries/01-pandas/README.md), targeting pandas 3.0.x, and [`openpyxl`](external-libraries/02-openpyxl/README.md), targeting openpyxl 3.1.x.
+The project foundation and **Phases 1–8 are complete**. **Phase 9: External Libraries is in progress** with three reviewed-track chapters: [`pandas`](external-libraries/01-pandas/README.md), targeting pandas 3.0.x, [`openpyxl`](external-libraries/02-openpyxl/README.md), targeting openpyxl 3.1.x, and [`requests`](external-libraries/03-requests/README.md), targeting Requests 2.34.x.
-The phase now covers both tabular-data transformation and Excel workbook automation. Repository CI installs the explicit third-party contract in [`requirements-external.txt`](requirements-external.txt) before running approved examples.
+The phase now covers tabular-data transformation, Excel workbook automation, and HTTP/API consumption. Repository CI installs the explicit third-party contract in [`requirements-external.txt`](requirements-external.txt) before running approved examples.
-The next planned Phase 9 chapters are `requests` and `pytest`. See the [External Libraries index](external-libraries/README.md), [roadmap](docs/roadmap.en.md), or [full learning path](docs/learning-path.en.md) for current status.
+The next planned Phase 9 chapter is `pytest`. See the [External Libraries index](external-libraries/README.md), [roadmap](docs/roadmap.en.md), or [full learning path](docs/learning-path.en.md) for current status.
## Visual identity
diff --git a/docs/learning-path.en.md b/docs/learning-path.en.md
index f7f8b1b..8cc64b6 100644
--- a/docs/learning-path.en.md
+++ b/docs/learning-path.en.md
@@ -121,10 +121,10 @@ Phase 8 is complete with nine reviewed chapters. The sequence moves from path mo
1. ✅ [`pandas`: Working with Tabular Data](../external-libraries/01-pandas/README.md)
2. ✅ [`openpyxl`: Automating Excel Workbooks](../external-libraries/02-openpyxl/README.md)
-3. ⏳ `requests`
+3. ✅ [`requests`: Consuming HTTP APIs](../external-libraries/03-requests/README.md)
4. ⏳ `pytest`
-Phase 9 is in progress. Chapter 01 establishes labeled/tabular data contracts with pandas 3.0.x. Chapter 02 adds openpyxl 3.1.x workbook automation: worksheet/cell access, formulas versus cached values, styles, tables, validations, optimized read/write modes, VBA preservation boundaries, round-trip risks, and deterministic workbook verification.
+Phase 9 is in progress. Chapter 01 establishes labeled/tabular data contracts with pandas 3.0.x. Chapter 02 adds openpyxl 3.1.x workbook automation. Chapter 03 adds Requests 2.34.x HTTP/API contracts: methods, query parameters, headers, JSON, timeouts, exception boundaries, Sessions, TLS verification, streaming, retries, secret redaction, pagination, and deterministic local-server testing.
## Phase 10 · Practical Projects ⏳
diff --git a/docs/learning-path.es.md b/docs/learning-path.es.md
index 4da9102..9c3b19b 100644
--- a/docs/learning-path.es.md
+++ b/docs/learning-path.es.md
@@ -121,10 +121,10 @@ La Fase 8 está completada con nueve capítulos revisados. La secuencia avanza d
1. ✅ [`pandas`: Trabajando con Datos Tabulares](../external-libraries/01-pandas/README.es.md)
2. ✅ [`openpyxl`: Automatizando Libros de Excel](../external-libraries/02-openpyxl/README.es.md)
-3. ⏳ `requests`
+3. ✅ [`requests`: Consumiendo APIs HTTP](../external-libraries/03-requests/README.es.md)
4. ⏳ `pytest`
-La Fase 9 está en progreso. El Capítulo 01 establece contratos de datos etiquetados/tabulares con pandas 3.0.x. El Capítulo 02 añade automatización de libros con openpyxl 3.1.x: acceso a hojas/celdas, fórmulas frente a valores en caché, estilos, tablas, validaciones, modos optimizados de lectura/escritura, límites de preservación de VBA, riesgos de round-trip y verificación determinista del libro.
+La Fase 9 está en progreso. El Capítulo 01 establece contratos de datos etiquetados/tabulares con pandas 3.0.x. El Capítulo 02 añade automatización de libros con openpyxl 3.1.x. El Capítulo 03 añade contratos HTTP/API con Requests 2.34.x: métodos, parámetros de query, headers, JSON, timeouts, fronteras de excepción, Sessions, verificación TLS, streaming, retries, redacción de secretos, paginación y pruebas deterministas con servidor local.
## Fase 10 · Proyectos Prácticos ⏳
diff --git a/docs/learning-path.pt-BR.md b/docs/learning-path.pt-BR.md
index 47b4eee..9d57ed1 100644
--- a/docs/learning-path.pt-BR.md
+++ b/docs/learning-path.pt-BR.md
@@ -121,10 +121,10 @@ A Fase 8 está concluída com nove capítulos revisados. A sequência avança de
1. ✅ [`pandas`: Trabalhando com Dados Tabulares](../external-libraries/01-pandas/README.pt-BR.md)
2. ✅ [`openpyxl`: Automatizando Workbooks do Excel](../external-libraries/02-openpyxl/README.pt-BR.md)
-3. ⏳ `requests`
+3. ✅ [`requests`: Consumindo APIs HTTP](../external-libraries/03-requests/README.pt-BR.md)
4. ⏳ `pytest`
-A Fase 9 está em andamento. O Capítulo 01 estabelece contratos de dados rotulados/tabulares com pandas 3.0.x. O Capítulo 02 acrescenta automação de workbooks com openpyxl 3.1.x: acesso a planilhas/células, fórmulas versus valores em cache, estilos, tabelas, validações, modos otimizados de leitura/escrita, limites de preservação de VBA, riscos de round-trip e verificação determinística do workbook.
+A Fase 9 está em andamento. O Capítulo 01 estabelece contratos de dados rotulados/tabulares com pandas 3.0.x. O Capítulo 02 acrescenta automação de workbooks com openpyxl 3.1.x. O Capítulo 03 acrescenta contratos HTTP/API com Requests 2.34.x: métodos, parâmetros de query, headers, JSON, timeouts, fronteiras de exceção, Sessions, verificação TLS, streaming, retries, redação de segredos, paginação e testes determinísticos com servidor local.
## Fase 10 · Projetos Práticos ⏳
diff --git a/docs/localized/README.es.md b/docs/localized/README.es.md
index cd19d4a..f2df644 100644
--- a/docs/localized/README.es.md
+++ b/docs/localized/README.es.md
@@ -74,11 +74,11 @@ Explicaciones detalladas:
## Estado actual
-La base del proyecto y las **Fases 1–8 están completadas**. La **Fase 9: Bibliotecas Externas está en progreso** con dos capítulos revisados de la ruta: [`pandas`](../../external-libraries/01-pandas/README.es.md), apuntando a pandas 3.0.x, y [`openpyxl`](../../external-libraries/02-openpyxl/README.es.md), apuntando a openpyxl 3.1.x.
+La base del proyecto y las **Fases 1–8 están completadas**. La **Fase 9: Bibliotecas Externas está en progreso** con tres capítulos revisados de la ruta: [`pandas`](../../external-libraries/01-pandas/README.es.md), apuntando a pandas 3.0.x, [`openpyxl`](../../external-libraries/02-openpyxl/README.es.md), apuntando a openpyxl 3.1.x, y [`requests`](../../external-libraries/03-requests/README.es.md), apuntando a Requests 2.34.x.
-La fase ahora cubre tanto transformación de datos tabulares como automatización de libros de Excel. El CI del repositorio instala el contrato explícito de terceros en [`requirements-external.txt`](../../requirements-external.txt) antes de ejecutar los ejemplos aprobados.
+La fase ahora cubre transformación de datos tabulares, automatización de libros de Excel y consumo HTTP/API. El CI del repositorio instala el contrato explícito de terceros en [`requirements-external.txt`](../../requirements-external.txt) antes de ejecutar los ejemplos aprobados.
-Los siguientes capítulos planificados de la Fase 9 son `requests` y `pytest`. Consulta el [índice de Bibliotecas Externas](../../external-libraries/README.es.md), el [roadmap](../roadmap.es.md) o la [ruta completa de aprendizaje](../learning-path.es.md) para el estado actualizado.
+El siguiente capítulo planificado de la Fase 9 es `pytest`. Consulta el [índice de Bibliotecas Externas](../../external-libraries/README.es.md), el [roadmap](../roadmap.es.md) o la [ruta completa de aprendizaje](../learning-path.es.md) para el estado actualizado.
## Identidad visual
diff --git a/docs/localized/README.pt-BR.md b/docs/localized/README.pt-BR.md
index 483bdbd..5d7feeb 100644
--- a/docs/localized/README.pt-BR.md
+++ b/docs/localized/README.pt-BR.md
@@ -74,11 +74,11 @@ Explicações detalhadas:
## Status atual
-A fundação do projeto e as **Fases 1–8 estão concluídas**. A **Fase 9: Bibliotecas Externas está em andamento** com dois capítulos revisados da trilha: [`pandas`](../../external-libraries/01-pandas/README.pt-BR.md), tendo pandas 3.0.x como alvo, e [`openpyxl`](../../external-libraries/02-openpyxl/README.pt-BR.md), tendo openpyxl 3.1.x como alvo.
+A fundação do projeto e as **Fases 1–8 estão concluídas**. A **Fase 9: Bibliotecas Externas está em andamento** com três capítulos revisados da trilha: [`pandas`](../../external-libraries/01-pandas/README.pt-BR.md), tendo pandas 3.0.x como alvo, [`openpyxl`](../../external-libraries/02-openpyxl/README.pt-BR.md), tendo openpyxl 3.1.x como alvo, e [`requests`](../../external-libraries/03-requests/README.pt-BR.md), tendo Requests 2.34.x como alvo.
-A fase agora cobre tanto transformação de dados tabulares quanto automação de workbooks do Excel. O CI do repositório instala o contrato explícito de terceiros em [`requirements-external.txt`](../../requirements-external.txt) antes de executar os exemplos aprovados.
+A fase agora cobre transformação de dados tabulares, automação de workbooks do Excel e consumo HTTP/API. O CI do repositório instala o contrato explícito de terceiros em [`requirements-external.txt`](../../requirements-external.txt) antes de executar os exemplos aprovados.
-Os próximos capítulos planejados da Fase 9 são `requests` e `pytest`. Consulte o [índice de Bibliotecas Externas](../../external-libraries/README.pt-BR.md), o [roadmap](../roadmap.pt-BR.md) ou a [trilha completa de estudos](../learning-path.pt-BR.md) para o status atualizado.
+O próximo capítulo planejado da Fase 9 é `pytest`. Consulte o [índice de Bibliotecas Externas](../../external-libraries/README.pt-BR.md), o [roadmap](../roadmap.pt-BR.md) ou a [trilha completa de estudos](../learning-path.pt-BR.md) para o status atualizado.
## Identidade visual
diff --git a/docs/project-structure.en.md b/docs/project-structure.en.md
index c966eb7..e7a8ee3 100644
--- a/docs/project-structure.en.md
+++ b/docs/project-structure.en.md
@@ -203,16 +203,26 @@ python-study-guide/
│ │ ├── filter_and_assign.py
│ │ ├── groupby_summary.py
│ │ └── merge_tables.py
-│ └── 02-openpyxl/
+│ ├── 02-openpyxl/
+│ │ ├── README.md
+│ │ ├── README.pt-BR.md
+│ │ ├── README.es.md
+│ │ └── examples/
+│ │ ├── load_and_iterate.py
+│ │ ├── styled_report.py
+│ │ ├── table_and_validation.py
+│ │ ├── workbook_basics.py
+│ │ └── write_only_export.py
+│ └── 03-requests/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
│ └── examples/
-│ ├── load_and_iterate.py
-│ ├── styled_report.py
-│ ├── table_and_validation.py
-│ ├── workbook_basics.py
-│ └── write_only_export.py
+│ ├── get_with_query.py
+│ ├── http_error_handling.py
+│ ├── post_json.py
+│ ├── session_defaults.py
+│ └── stream_download.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -548,7 +558,7 @@ python-study-guide/
- `docs/`: master learning paths, roadmaps, project architecture, localized project documents, policies, and responsible AI-assisted development guidance.
- `errors-files-and-modules/`: complete Phase 7 learning path. Chapters 01–05 cover runtime exception handling, deliberate raising and custom exceptions, safe text-file I/O with `open()` and `with`, TXT/CSV/JSON parsing and writing, and code organization through imports, modules, regular packages, execution context, and dependency design, in English, Brazilian Portuguese, and Spanish with deterministic executable examples.
- `exercises/`: focused practice activities connected to learning chapters.
-- `external-libraries/`: Phase 9 learning path for third-party packages. It currently contains reviewed multilingual chapters for pandas 3.0.x and openpyxl 3.1.x with ten deterministic executable examples in total; `requests` and `pytest` are planned next.
+- `external-libraries/`: Phase 9 learning path for third-party packages. It currently contains reviewed multilingual chapters for pandas 3.0.x, openpyxl 3.1.x, and Requests 2.34.x with fifteen deterministic executable examples in total; `pytest` is planned next.
- `functions/`: complete Phase 5 learning path. Chapters 01–09 cover defining and calling functions, required inputs, returned values, scope and name lookup, type hints for function interfaces, default values including definition-time evaluation and mutable-default safety, variable-length positional and keyword argument collection with `*args` and `**kwargs`, composition through helper and coordinating functions with explicit dependencies and simple call graphs, and explicit data-flow tracing across calls including parameter bindings, rebinding versus mutation, `None`, tuple results, and return-based handoffs, in English, Brazilian Portuguese, and Spanish with deterministic executable examples.
- `fundamentals/`: complete Phase 1 learning path. Its six chapters teach how Python runs a program, how to use `print()` and `input()`, how assignment and naming work, how to recognize and inspect common built-in data types, and how to convert compatible values deliberately, with aligned multilingual explanations and executable examples.
- `practical-projects/`: future small projects combining several concepts.
diff --git a/docs/project-structure.es.md b/docs/project-structure.es.md
index b4276a9..84754d5 100644
--- a/docs/project-structure.es.md
+++ b/docs/project-structure.es.md
@@ -203,16 +203,26 @@ python-study-guide/
│ │ ├── filter_and_assign.py
│ │ ├── groupby_summary.py
│ │ └── merge_tables.py
-│ └── 02-openpyxl/
+│ ├── 02-openpyxl/
+│ │ ├── README.md
+│ │ ├── README.pt-BR.md
+│ │ ├── README.es.md
+│ │ └── examples/
+│ │ ├── load_and_iterate.py
+│ │ ├── styled_report.py
+│ │ ├── table_and_validation.py
+│ │ ├── workbook_basics.py
+│ │ └── write_only_export.py
+│ └── 03-requests/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
│ └── examples/
-│ ├── load_and_iterate.py
-│ ├── styled_report.py
-│ ├── table_and_validation.py
-│ ├── workbook_basics.py
-│ └── write_only_export.py
+│ ├── get_with_query.py
+│ ├── http_error_handling.py
+│ ├── post_json.py
+│ ├── session_defaults.py
+│ └── stream_download.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -548,7 +558,7 @@ python-study-guide/
- `docs/`: rutas completas de aprendizaje, roadmaps, arquitectura del proyecto, documentos localizados, políticas y guía de desarrollo responsable asistido por IA.
- `errors-files-and-modules/`: ruta completa de la Fase 7. Los Capítulos 01–05 cubren manejo de excepciones en runtime, lanzamiento deliberado y excepciones personalizadas, I/O seguro de archivos de texto con `open()` y `with`, parsing y escritura de TXT/CSV/JSON y organización del código mediante imports, módulos, paquetes regulares, contexto de ejecución y diseño de dependencias, en inglés, portugués de Brasil y español con ejemplos ejecutables deterministas.
- `exercises/`: actividades prácticas relacionadas con los capítulos.
-- `external-libraries/`: ruta de la Fase 9 para paquetes de terceros. Actualmente contiene capítulos multilingües revisados de pandas 3.0.x y openpyxl 3.1.x con diez ejemplos ejecutables deterministas en total; `requests` y `pytest` son los siguientes planificados.
+- `external-libraries/`: ruta de la Fase 9 para paquetes de terceros. Actualmente contiene capítulos multilingües revisados de pandas 3.0.x, openpyxl 3.1.x y Requests 2.34.x con quince ejemplos ejecutables deterministas en total; `pytest` es el siguiente planificado.
- `functions/`: ruta completa de la Fase 5. Los Capítulos 01–09 cubren definición y llamada de funciones, entradas obligatorias, valores retornados, alcance y búsqueda de nombres, type hints para interfaces de funciones, valores predeterminados incluida la evaluación al definir la función y la seguridad con valores mutables, recolección de argumentos posicionales y por palabra clave de cantidad variable con `*args` y `**kwargs`, composición mediante funciones auxiliares y coordinadoras con dependencias explícitas y grafos simples de llamadas, y seguimiento explícito del flujo de datos entre llamadas, incluidos vínculos de parámetros, reasignación frente a mutación, `None`, resultados en tupla y traspasos mediante `return`, en inglés, portugués de Brasil y español, con ejemplos ejecutables determinísticos.
- `fundamentals/`: ruta completa de la Fase 1. Sus seis capítulos enseñan cómo Python ejecuta un programa, cómo usar `print()` e `input()`, cómo funcionan la asignación y los nombres, cómo reconocer e inspeccionar tipos de datos incorporados comunes y cómo convertir valores compatibles de forma deliberada, con explicaciones multilingües alineadas y ejemplos ejecutables.
- `practical-projects/`: futuros proyectos pequeños que combinarán varios conceptos.
diff --git a/docs/project-structure.pt-BR.md b/docs/project-structure.pt-BR.md
index de0891d..180bc2e 100644
--- a/docs/project-structure.pt-BR.md
+++ b/docs/project-structure.pt-BR.md
@@ -203,16 +203,26 @@ python-study-guide/
│ │ ├── filter_and_assign.py
│ │ ├── groupby_summary.py
│ │ └── merge_tables.py
-│ └── 02-openpyxl/
+│ ├── 02-openpyxl/
+│ │ ├── README.md
+│ │ ├── README.pt-BR.md
+│ │ ├── README.es.md
+│ │ └── examples/
+│ │ ├── load_and_iterate.py
+│ │ ├── styled_report.py
+│ │ ├── table_and_validation.py
+│ │ ├── workbook_basics.py
+│ │ └── write_only_export.py
+│ └── 03-requests/
│ ├── README.md
│ ├── README.pt-BR.md
│ ├── README.es.md
│ └── examples/
-│ ├── load_and_iterate.py
-│ ├── styled_report.py
-│ ├── table_and_validation.py
-│ ├── workbook_basics.py
-│ └── write_only_export.py
+│ ├── get_with_query.py
+│ ├── http_error_handling.py
+│ ├── post_json.py
+│ ├── session_defaults.py
+│ └── stream_download.py
├── functions/
│ ├── README.md
│ ├── README.pt-BR.md
@@ -548,7 +558,7 @@ python-study-guide/
- `docs/`: trilhas completas de estudos, roadmaps, arquitetura do projeto, documentos localizados, políticas e guia de desenvolvimento responsável assistido por IA.
- `errors-files-and-modules/`: trilha completa da Fase 7. Os Capítulos 01–05 cobrem tratamento de exceções em runtime, levantamento deliberado e exceções personalizadas, I/O seguro de arquivos de texto com `open()` e `with`, parsing e escrita de TXT/CSV/JSON e organização do código por imports, módulos, pacotes regulares, contexto de execução e design de dependências, em inglês, português brasileiro e espanhol com exemplos executáveis determinísticos.
- `exercises/`: atividades práticas relacionadas aos capítulos.
-- `external-libraries/`: trilha da Fase 9 para pacotes de terceiros. Atualmente contém capítulos multilíngues revisados de pandas 3.0.x e openpyxl 3.1.x com dez exemplos executáveis determinísticos no total; `requests` e `pytest` são os próximos planejados.
+- `external-libraries/`: trilha da Fase 9 para pacotes de terceiros. Atualmente contém capítulos multilíngues revisados de pandas 3.0.x, openpyxl 3.1.x e Requests 2.34.x com quinze exemplos executáveis determinísticos no total; `pytest` é o próximo planejado.
- `functions/`: trilha completa da Fase 5. Os Capítulos 01–09 cobrem definição e chamada de funções, entradas obrigatórias, valores retornados, escopo e busca de nomes, type hints para interfaces de funções, valores padrão incluindo avaliação no momento da definição e segurança com padrões mutáveis, coleta de argumentos posicionais e nomeados de quantidade variável com `*args` e `**kwargs`, composição por funções auxiliares e coordenadoras com dependências explícitas e grafos simples de chamadas e rastreamento explícito do fluxo de dados entre chamadas, incluindo vínculos de parâmetros, reatribuição versus mutação, `None`, resultados em tupla e passagens por `return`, em inglês, português brasileiro e espanhol com exemplos executáveis determinísticos.
- `fundamentals/`: trilha completa da Fase 1. Seus seis capítulos ensinam como o Python executa um programa, como usar `print()` e `input()`, como funcionam atribuição e nomes, como reconhecer e inspecionar tipos de dados embutidos comuns e como converter valores compatíveis de forma deliberada, com explicações multilíngues alinhadas e exemplos executáveis.
- `practical-projects/`: futuros projetos pequenos combinando diversos conceitos.
diff --git a/docs/roadmap.en.md b/docs/roadmap.en.md
index a593493..770e302 100644
--- a/docs/roadmap.en.md
+++ b/docs/roadmap.en.md
@@ -158,10 +158,10 @@ See the [section learning path](../external-libraries/README.md).
- [x] [`pandas`](../external-libraries/01-pandas/README.md)
- [x] [`openpyxl`](../external-libraries/02-openpyxl/README.md)
-- [ ] `requests`
+- [x] [`requests`](../external-libraries/03-requests/README.md)
- [ ] `pytest`
-Phase 9 is in progress. Chapter 01 introduces pandas 3.0.x for labeled tabular data, selection, Copy-on-Write, missing-data policy, vectorized transformations, grouping, validated joins, reshaping, and CSV pipelines. Chapter 02 adds openpyxl 3.1.x for Excel workbook creation/loading, formulas and cached values, styles, worksheet tables, data-validation metadata, optimized read/write modes, macro-preservation boundaries, safe round trips, and deterministic workbook verification. Executable external-library examples use the dependency contract declared in [`requirements-external.txt`](../requirements-external.txt).
+Phase 9 is in progress. Chapter 01 introduces pandas 3.0.x for labeled tabular data. Chapter 02 adds openpyxl 3.1.x for Excel workbook automation. Chapter 03 adds Requests 2.34.x for HTTP/API consumption with explicit methods, query/header/body contracts, timeouts, exception handling, Sessions, TLS verification, streaming, retry safety, pagination, secret redaction, and deterministic local HTTP tests. Executable external-library examples use the dependency contract declared in [`requirements-external.txt`](../requirements-external.txt).
## Phase 10: Practical projects
diff --git a/docs/roadmap.es.md b/docs/roadmap.es.md
index 17dc2ed..0b610e1 100644
--- a/docs/roadmap.es.md
+++ b/docs/roadmap.es.md
@@ -158,10 +158,10 @@ Consulta la [ruta de aprendizaje de la sección](../external-libraries/README.es
- [x] [`pandas`](../external-libraries/01-pandas/README.es.md)
- [x] [`openpyxl`](../external-libraries/02-openpyxl/README.es.md)
-- [ ] `requests`
+- [x] [`requests`](../external-libraries/03-requests/README.es.md)
- [ ] `pytest`
-La Fase 9 está en progreso. El Capítulo 01 introduce pandas 3.0.x para datos tabulares etiquetados, selección, Copy-on-Write, política de datos ausentes, transformaciones vectorizadas, agrupaciones, joins validados, reshape y pipelines CSV. El Capítulo 02 añade openpyxl 3.1.x para creación/carga de libros de Excel, fórmulas y valores en caché, estilos, tablas, metadatos de validación, modos optimizados de lectura/escritura, límites de preservación de macros, round-trips seguros y verificación determinista de libros. Los ejemplos ejecutables de bibliotecas externas usan el contrato declarado en [`requirements-external.txt`](../requirements-external.txt).
+La Fase 9 está en progreso. El Capítulo 01 introduce pandas 3.0.x para datos tabulares etiquetados. El Capítulo 02 añade openpyxl 3.1.x para automatización de libros de Excel. El Capítulo 03 añade Requests 2.34.x para consumo HTTP/API con contratos explícitos de métodos, query/headers/cuerpos, timeouts, manejo de excepciones, Sessions, verificación TLS, streaming, seguridad de retries, paginación, redacción de secretos y pruebas HTTP locales deterministas. Los ejemplos ejecutables de bibliotecas externas usan el contrato declarado en [`requirements-external.txt`](../requirements-external.txt).
## Fase 10: Proyectos prácticos
diff --git a/docs/roadmap.pt-BR.md b/docs/roadmap.pt-BR.md
index 77c9783..cb9fa3b 100644
--- a/docs/roadmap.pt-BR.md
+++ b/docs/roadmap.pt-BR.md
@@ -158,10 +158,10 @@ Veja a [trilha de aprendizagem da seção](../external-libraries/README.pt-BR.md
- [x] [`pandas`](../external-libraries/01-pandas/README.pt-BR.md)
- [x] [`openpyxl`](../external-libraries/02-openpyxl/README.pt-BR.md)
-- [ ] `requests`
+- [x] [`requests`](../external-libraries/03-requests/README.pt-BR.md)
- [ ] `pytest`
-A Fase 9 está em andamento. O Capítulo 01 introduz pandas 3.0.x para dados tabulares rotulados, seleção, Copy-on-Write, política de dados ausentes, transformações vetorizadas, agrupamentos, joins validados, reshape e pipelines CSV. O Capítulo 02 acrescenta openpyxl 3.1.x para criação/carregamento de workbooks do Excel, fórmulas e valores em cache, estilos, tabelas, metadados de validação, modos otimizados de leitura/escrita, limites de preservação de macros, round-trips seguros e verificação determinística de workbooks. Os exemplos executáveis de bibliotecas externas usam o contrato declarado em [`requirements-external.txt`](../requirements-external.txt).
+A Fase 9 está em andamento. O Capítulo 01 introduz pandas 3.0.x para dados tabulares rotulados. O Capítulo 02 acrescenta openpyxl 3.1.x para automação de workbooks do Excel. O Capítulo 03 acrescenta Requests 2.34.x para consumo HTTP/API com contratos explícitos de métodos, query/headers/corpos, timeouts, tratamento de exceções, Sessions, verificação TLS, streaming, segurança de retries, paginação, redação de segredos e testes HTTP locais determinísticos. Os exemplos executáveis de bibliotecas externas usam o contrato declarado em [`requirements-external.txt`](../requirements-external.txt).
## Fase 10: Projetos práticos
diff --git a/external-libraries/02-openpyxl/README.es.md b/external-libraries/02-openpyxl/README.es.md
index aec198c..d004058 100644
--- a/external-libraries/02-openpyxl/README.es.md
+++ b/external-libraries/02-openpyxl/README.es.md
@@ -1134,6 +1134,6 @@ pandas -> transform tabular data
openpyxl -> construct and maintain Excel workbooks
```
-La próxima biblioteca planificada es **`requests`**, donde la frontera pasa de archivos locales a servicios HTTP y APIs.
+Continúa con **[`requests`: Consumiendo APIs HTTP](../03-requests/README.es.md)**, donde la frontera pasa de archivos locales a servicios HTTP y APIs.
Antes de continuar, practica generando libros que puedas inspeccionar manualmente y validar automáticamente. La automatización de hojas de cálculo se vuelve confiable cuando tanto el contrato de datos como el contrato del workbook son explícitos.
diff --git a/external-libraries/02-openpyxl/README.md b/external-libraries/02-openpyxl/README.md
index 365f9f6..b951979 100644
--- a/external-libraries/02-openpyxl/README.md
+++ b/external-libraries/02-openpyxl/README.md
@@ -1134,6 +1134,6 @@ pandas -> transform tabular data
openpyxl -> construct and maintain Excel workbooks
```
-The next planned library is **`requests`**, where the boundary moves from local files to HTTP services and APIs.
+Continue with **[`requests`: Consuming HTTP APIs](../03-requests/README.md)**, where the boundary moves from local files to HTTP services and APIs.
Before moving on, practice by generating workbooks that you can inspect manually and validate automatically. Spreadsheet automation becomes reliable when both the data contract and the workbook contract are explicit.
diff --git a/external-libraries/02-openpyxl/README.pt-BR.md b/external-libraries/02-openpyxl/README.pt-BR.md
index 0cf3538..d282c84 100644
--- a/external-libraries/02-openpyxl/README.pt-BR.md
+++ b/external-libraries/02-openpyxl/README.pt-BR.md
@@ -1134,6 +1134,6 @@ pandas -> transform tabular data
openpyxl -> construct and maintain Excel workbooks
```
-A próxima biblioteca planejada é **`requests`**, quando a fronteira deixa arquivos locais e passa para serviços HTTP e APIs.
+Continue com **[`requests`: Consumindo APIs HTTP](../03-requests/README.pt-BR.md)**, quando a fronteira deixa arquivos locais e passa para serviços HTTP e APIs.
Antes de avançar, pratique gerando workbooks que possam ser inspecionados manualmente e validados automaticamente. Automação de planilhas fica confiável quando tanto o contrato de dados quanto o contrato do workbook são explícitos.
diff --git a/external-libraries/03-requests/README.es.md b/external-libraries/03-requests/README.es.md
new file mode 100644
index 0000000..cd251fd
--- /dev/null
+++ b/external-libraries/03-requests/README.es.md
@@ -0,0 +1,998 @@
+
+
+# Consumiendo APIs HTTP con `requests`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Volver a Bibliotecas Externas](../README.es.md) · [← Anterior: `openpyxl`](../02-openpyxl/README.es.md)
+
+Los archivos locales son solo un tipo de frontera. Los programas Python modernos también intercambian datos con servicios web, APIs internas, plataformas SaaS, servidores de autenticación, endpoints de almacenamiento y otros sistemas mediante HTTP. El paquete `requests` ofrece a Python un cliente HTTP compacto y legible, manteniendo visibles los conceptos de protocolo necesarios para integraciones confiables.
+
+Este capítulo apunta a la serie **Requests 2.34.x** y fue investigado contra la documentación y los metadatos actuales de **Requests 2.34.2**. Requests 2.34.2 requiere Python 3.10 o superior; este repositorio valida los ejemplos en Python 3.13.
+
+**Tiempo estimado de estudio:** 270–360 minutos.
+
+## Objetivos de aprendizaje
+
+Al finalizar este capítulo, deberías poder:
+
+- explicar el modelo HTTP de solicitud/respuesta sin tratar una llamada de API como una función mágica;
+- ejecutar GET, POST, PUT, PATCH y DELETE de forma intencional;
+- enviar parámetros de query, headers, datos de formulario, cuerpos JSON, archivos y datos de autenticación;
+- distinguir éxito de transporte, éxito HTTP y validez del payload;
+- configurar timeouts de conexión/lectura y entender lo que no garantizan;
+- manejar excepciones de Requests sin ocultar contexto útil;
+- usar `Session` para reutilización de conexiones, cookies y valores predeterminados compartidos;
+- comprender redirects, verificación TLS, bundles de CA, proxies y configuración de entorno;
+- procesar respuestas grandes mediante streaming y cerrar recursos de forma determinista;
+- añadir retries solo cuando una operación pueda repetirse con seguridad;
+- proteger credenciales y evitar registrar secretos;
+- validar contratos de respuesta en vez de confiar en JSON arbitrario;
+- construir pruebas HTTP deterministas sin depender de un servicio público de internet.
+
+## 1. Por qué existe `requests`
+
+La biblioteca estándar de Python puede hablar HTTP, pero `requests` ofrece una interfaz de mayor nivel para tareas habituales de cliente: URLs, parámetros de query, headers, cookies, autenticación, cuerpos de solicitud, verificación TLS, Sessions, streaming y excepciones.
+
+La comodidad es valiosa, pero la red sigue siendo una frontera de sistema distribuido. Una API legible no elimina latencia, fallas parciales, reglas de autenticación, errores del servidor, retries ni decisiones de seguridad.
+
+## 2. Piensa en solicitudes y respuestas
+
+Un cliente envía una solicitud HTTP que contiene alguna combinación de:
+
+```text
+method + URL + headers + optional body
+```
+
+El servidor devuelve una respuesta HTTP que contiene:
+
+```text
+status code + headers + body
+```
+
+Tu código Python debe razonar sobre ambas mitades.
+
+## 3. Las bibliotecas externas necesitan un contrato de versión
+
+Este repositorio declara las dependencias de la Fase 9 en `requirements-external.txt`.
+
+Para este capítulo el contrato es:
+
+```text
+requests >= 2.34 and < 2.35
+```
+
+La serie 2.34 también introdujo tipado inline dentro de Requests, de modo que los type checkers actuales pueden consumir los tipos de la API pública sin depender de un paquete separado de stubs.
+
+## 4. Instala el conjunto de dependencias del repositorio
+
+Crea y activa un entorno virtual y luego instala:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+Para experimentación aislada, `python -m pip install requests` es válido, pero un proyecto debe registrar el rango de dependencias compatible.
+
+## 5. Importa el paquete
+
+El import convencional es:
+
+```python
+import requests
+```
+
+El módulo de nivel superior expone funciones cómodas como `get()`, `post()` y `delete()`, además de clases como `Session` y tipos de excepción bajo `requests.exceptions`.
+
+## 6. Una URL forma parte del contrato de entrada
+
+Una URL HTTP normalmente contiene:
+
+```text
+scheme://host:port/path?query#fragment
+```
+
+Para un cliente HTTP, scheme, host, port, path y parámetros de query afectan la solicitud real. Los fragments normalmente se interpretan del lado cliente y no se envían como objetivo de la solicitud HTTP.
+
+No concatenes fragmentos de URL no confiables de forma descuidada.
+
+## 7. Empieza con una solicitud GET
+
+Un GET básico se ve así:
+
+```python
+import requests
+
+
+response = requests.get("https://example.com/api/items", timeout=(3, 10))
+print(response.status_code)
+```
+
+Este snippet es ilustrativo porque depende de un endpoint externo. Los ejemplos ejecutables publicados más adelante usan un servidor local de prueba.
+
+## 8. Los status codes comunican resultados HTTP
+
+Clases comunes:
+
+```text
+1xx -> informational
+2xx -> successful response
+3xx -> redirection
+4xx -> client-side request problem
+5xx -> server-side failure
+```
+
+`200` no es el único resultado exitoso. Un POST puede devolver correctamente `201 Created`, y un DELETE puede devolver `204 No Content`.
+
+## 9. Usa `raise_for_status()` cuando los códigos HTTP no exitosos sean fallas
+
+```python
+response = requests.get(url, timeout=(3, 10))
+response.raise_for_status()
+```
+
+Para status HTTP no exitosos, `raise_for_status()` lanza `requests.HTTPError` y conserva la respuesta asociada a la excepción.
+
+No descartes ese contexto al informar una falla.
+
+## 10. Éxito de transporte no es éxito de aplicación
+
+Un intercambio HTTP puede funcionar a nivel de red mientras el servidor devuelve `404`, `429` o `500`.
+
+Del mismo modo, una respuesta `200` puede contener datos que violan tu contrato de negocio.
+
+Un cliente robusto verifica más que “¿`requests.get()` devolvió algo?”.
+
+## 11. `response.text` es texto decodificado
+
+```python
+text = response.text
+```
+
+Requests decodifica los bytes de respuesta según información de encoding y sus reglas de detección.
+
+Usa texto cuando el cuerpo sea realmente textual y necesites un `str`.
+
+## 12. `response.content` entrega bytes crudos
+
+```python
+payload = response.content
+```
+
+Usa bytes para archivos binarios, checksums, imágenes, artefactos comprimidos o cualquier formato donde decodificar a texto sería incorrecto.
+
+## 13. Decodifica JSON con `response.json()`
+
+```python
+response = requests.get(url, timeout=(3, 10))
+response.raise_for_status()
+data = response.json()
+```
+
+`response.json()` analiza el cuerpo. No demuestra que el status HTTP haya sido exitoso.
+
+## 14. JSON inválido tiene su propio modo de falla
+
+Requests expone `requests.exceptions.JSONDecodeError` para errores de decodificación JSON.
+
+```python
+try:
+ data = response.json()
+except requests.exceptions.JSONDecodeError as exc:
+ raise RuntimeError("API returned invalid JSON") from exc
+```
+
+Una respuesta `204 No Content`, una página HTML de error o JSON malformado pueden hacer fallar la decodificación.
+
+## 15. La forma del JSON todavía debe validarse
+
+Incluso JSON válido puede ser incorrecto para tu programa:
+
+```python
+if not isinstance(data, dict) or "items" not in data:
+ raise ValueError("Unexpected API response shape")
+```
+
+El parsing responde “¿esto es JSON?”. La validación de contrato responde “¿es el JSON que espera nuestra aplicación?”.
+
+## 16. Envía parámetros de query con `params`
+
+No construyas query strings a mano cuando Requests puede codificarlos:
+
+```python
+response = requests.get(
+ url,
+ params={"status": "open", "limit": 20},
+ timeout=(3, 10),
+)
+```
+
+Requests maneja el encoding de query y expone la URL final mediante `response.url`.
+
+## 17. Parámetros repetidos pueden usar secuencias
+
+Algunas APIs esperan claves repetidas. Requests acepta valores secuenciales o una lista de tuplas de dos elementos.
+
+```python
+params = [("tag", "python"), ("tag", "http")]
+response = requests.get(url, params=params, timeout=(3, 10))
+```
+
+Sigue el contrato documentado de la API objetivo.
+
+## 18. Los headers de solicitud son metadatos
+
+```python
+headers = {
+ "Accept": "application/json",
+ "User-Agent": "study-client/1.0",
+}
+response = requests.get(url, headers=headers, timeout=(3, 10))
+```
+
+Los headers pueden comunicar preferencias de representación, autenticación, solicitudes condicionales, tracing y otros metadatos de protocolo.
+
+## 19. No confundas headers de solicitud y respuesta
+
+Los headers de solicitud son los que envía el cliente. Los headers de respuesta son los que devuelve el servidor.
+
+```python
+content_type = response.headers.get("Content-Type")
+```
+
+Los headers de respuesta de Requests se comportan como un mapping case-insensitive.
+
+## 20. Usa un `User-Agent` descriptivo cuando corresponda
+
+Muchas APIs valoran un identificador de cliente para operaciones y diagnóstico de soporte.
+
+Evita fingir que eres un navegador no relacionado salvo que el contrato de integración lo exija explícitamente.
+
+## 21. Envía datos de formulario con `data=`
+
+Para datos estilo formulario:
+
+```python
+response = requests.post(
+ url,
+ data={"username": "demo", "mode": "compact"},
+ timeout=(3, 10),
+)
+```
+
+Requests codifica un mapping pasado mediante `data=` como datos de formulario.
+
+## 22. Envía JSON con `json=`
+
+Para APIs JSON, prefiere el parámetro dedicado:
+
+```python
+payload = {"name": "Nova", "active": True}
+response = requests.post(url, json=payload, timeout=(3, 10))
+response.raise_for_status()
+```
+
+`json=` serializa el valor y configura un content type JSON apropiado.
+
+## 23. No serialices JSON manualmente sin una razón
+
+Esto suele ser menos claro:
+
+```python
+import json
+
+body = json.dumps(payload)
+response = requests.post(
+ url,
+ data=body,
+ headers={"Content-Type": "application/json"},
+ timeout=(3, 10),
+)
+```
+
+Usa `json=` salvo que necesites control preciso sobre los bytes serializados.
+
+## 24. PUT y PATCH expresan contratos de actualización distintos
+
+La especificación HTTP y la documentación de cada API determinan la semántica. Comúnmente:
+
+```text
+PUT -> replace or set a representation at a target
+PATCH -> partially modify a representation
+```
+
+No deduzcas el comportamiento del servidor únicamente por el nombre del método. Lee el contrato de la API.
+
+## 25. DELETE puede tener éxito sin cuerpo
+
+```python
+response = requests.delete(url, timeout=(3, 10))
+response.raise_for_status()
+```
+
+Un `204 No Content` exitoso no debe ir seguido por un `response.json()` incondicional.
+
+## 26. La idempotencia importa antes de los retries
+
+Una operación es idempotente cuando repetir la misma solicitud pretendida tiene el mismo efecto pretendido que ejecutarla una vez.
+
+GET, HEAD, PUT y DELETE tienen semántica idempotente al nivel del método HTTP; POST en general no. El comportamiento de aplicación y las claves de idempotencia pueden agregar garantías adicionales.
+
+## 27. Casi toda solicitud de producción necesita timeout
+
+Requests **no** aplica timeout por defecto.
+
+```python
+response = requests.get(url, timeout=10)
+```
+
+Sin timeout explícito, un programa puede esperar indefinidamente actividad de red.
+
+## 28. Separa timeouts de conexión y lectura cuando sea útil
+
+Requests acepta una tupla:
+
+```python
+response = requests.get(url, timeout=(3, 15))
+```
+
+El primer valor es el connect timeout. El segundo es el read timeout.
+
+Esto suele ser más claro en integraciones donde establecer la conexión y esperar bytes de respuesta tienen expectativas distintas.
+
+## 29. Un timeout de Requests no es un deadline total de reloj
+
+El comportamiento documentado se basa en inactividad del socket, no en una duración máxima garantizada para la descarga completa.
+
+Una respuesta lenta que continúa enviando bytes puede durar más que el read timeout nominal.
+
+Si un flujo necesita un deadline global rígido, diseña ese deadline por separado.
+
+## 30. Requests tiene una jerarquía útil de excepciones
+
+Las excepciones específicas de Requests heredan de `requests.exceptions.RequestException`.
+
+Subclases importantes incluyen:
+
+```text
+HTTPError
+ConnectionError
+Timeout
+TooManyRedirects
+JSONDecodeError
+SSLError
+```
+
+Captura de manera específica cuando exista comportamiento de recuperación diferente.
+
+## 31. `Timeout` merece manejo explícito
+
+```python
+try:
+ response = requests.get(url, timeout=(3, 10))
+except requests.Timeout as exc:
+ raise RuntimeError("Remote service timed out") from exc
+```
+
+Un timeout es distinto de un HTTP `500`: el cliente puede no saber si el servidor procesó la solicitud.
+
+Esa incertidumbre es crucial antes de reintentar una escritura.
+
+## 32. `ConnectionError` cubre fallas de conexión de red
+
+Fallas de DNS, conexiones rechazadas y problemas de transporte relacionados pueden aparecer como `requests.ConnectionError`.
+
+No conviertas esto en un resultado vacío y exitoso. Conserva la señal de falla o aplica una política de recuperación explícita.
+
+## 33. `HTTPError` da acceso a la respuesta
+
+```python
+try:
+ response.raise_for_status()
+except requests.HTTPError as exc:
+ status = exc.response.status_code
+ raise RuntimeError(f"API returned HTTP {status}") from exc
+```
+
+Evita incluir cuerpos arbitrarios de respuesta en logs porque pueden contener secretos o datos personales.
+
+## 34. Un límite superior con `RequestException` puede añadir contexto
+
+En una frontera de aplicación, un wrapper puede agregar el nombre de la operación del servicio:
+
+```python
+try:
+ response = requests.get(url, timeout=(3, 10))
+ response.raise_for_status()
+except requests.RequestException as exc:
+ raise RuntimeError("Could not load catalog") from exc
+```
+
+No captures `Exception` solo para hacer desaparecer una falla de red.
+
+## 35. Los redirects tienen historial
+
+Requests sigue redirects en solicitudes GET comunes y expone respuestas previas mediante:
+
+```python
+for previous in response.history:
+ print(previous.status_code, previous.url)
+```
+
+La URL final queda disponible en `response.url`.
+
+## 36. Limita o desactiva redirects cuando el contrato lo requiera
+
+```python
+response = requests.get(
+ url,
+ allow_redirects=False,
+ timeout=(3, 10),
+)
+```
+
+Los redirects pueden importar para autenticación, auditoría, defensas SSRF y URLs firmadas.
+
+## 37. La autenticación Basic tiene un helper dedicado
+
+```python
+from requests.auth import HTTPBasicAuth
+
+
+response = requests.get(
+ url,
+ auth=HTTPBasicAuth("demo-user", "demo-password"),
+ timeout=(3, 10),
+)
+```
+
+Nunca codifiques credenciales reales en código fuente, ejemplos, commits o logs.
+
+## 38. Los Bearer tokens normalmente son headers
+
+```python
+headers = {"Authorization": f"Bearer {token}"}
+response = requests.get(url, headers=headers, timeout=(3, 10))
+```
+
+El token debe provenir de una fuente segura en runtime, como un secret manager o variable de entorno protegida, no de un archivo del repositorio.
+
+## 39. Redacta secretos de la observabilidad
+
+No registres valores completos de:
+
+```text
+Authorization
+Proxy-Authorization
+Cookie
+Set-Cookie
+API keys
+signed URLs
+client certificates or private keys
+```
+
+Un logging útil puede incluir método HTTP, host/path sanitizado, status code, tiempo transcurrido, número de retry e identificadores de correlación.
+
+## 40. `Session` conserva estado entre solicitudes
+
+```python
+import requests
+
+
+with requests.Session() as session:
+ session.headers.update({"Accept": "application/json"})
+ response = session.get(url, timeout=(3, 10))
+```
+
+Una Session puede conservar valores predeterminados y cookies entre llamadas.
+
+## 41. Las Sessions también reutilizan conexiones
+
+Requests Sessions usan connection pooling de urllib3. Varias llamadas al mismo host pueden reutilizar conexiones subyacentes en vez de establecer una conexión TCP/TLS nueva cada vez.
+
+Esto puede reducir de manera importante el overhead en flujos repetidos de API.
+
+## 42. Los defaults de Session pueden sobrescribirse por solicitud
+
+Headers, autenticación, cookies, proxies y otros parámetros a nivel de Session son defaults convenientes, no globales inmutables.
+
+Mantén la configuración compartida intencional para evitar heredar por accidente credenciales destinadas a otro servicio.
+
+## 43. Cierra Sessions de forma determinista
+
+Usa un context manager o llama `close()`:
+
+```python
+with requests.Session() as session:
+ response = session.get(url, timeout=(3, 10))
+ response.raise_for_status()
+```
+
+La propiedad de recursos debe ser visible en procesos de larga duración.
+
+## 44. Las cookies pueden persistir en una Session
+
+Un servidor puede configurar cookies en una respuesta y esperarlas en solicitudes posteriores. Una Session mantiene un cookie jar entre llamadas.
+
+Para clientes de API, la autenticación explícita basada en tokens suele ser más fácil de razonar, pero los flujos basados en cookies todavía existen.
+
+## 45. Las prepared requests exponen la solicitud exacta de salida
+
+Requests puede construir un `PreparedRequest` antes de enviarlo:
+
+```python
+from requests import Request, Session
+
+
+with Session() as session:
+ request = Request("GET", url, headers={"X-Trace": "demo"})
+ prepared = session.prepare_request(request)
+ print(prepared.method, prepared.url)
+```
+
+Esto es útil para firma avanzada, inspección o mutación controlada de la solicitud.
+
+## 46. Prefiere `Session.prepare_request()` cuando el estado de Session importa
+
+Llamar `Request.prepare()` directamente no aplica automáticamente todo el estado a nivel de Session.
+
+Si cookies, headers predeterminados o autenticación de Session forman parte del contrato, prepara la solicitud mediante esa Session.
+
+## 47. Los flujos con prepared requests necesitan considerar el entorno
+
+La documentación avanzada de Requests advierte que enviar manualmente una prepared request puede omitir configuraciones derivadas del entorno si no se combinan explícitamente.
+
+Esto importa para settings como CA bundles y proxies.
+
+La preparación avanzada de solicitudes debe ser deliberada, no el patrón predeterminado para llamadas normales.
+
+## 48. La verificación de certificados HTTPS está habilitada por defecto
+
+Requests verifica certificados TLS del servidor para conexiones HTTPS.
+
+```python
+response = requests.get("https://example.com", timeout=(3, 10))
+```
+
+Si falla la verificación del certificado, Requests lanza `SSLError` en vez de confiar silenciosamente en el peer.
+
+## 49. `verify=False` desactiva una garantía importante de seguridad
+
+```python
+response = requests.get(url, verify=False, timeout=(3, 10))
+```
+
+Esto acepta certificados que pueden estar expirados, ser autofirmados o corresponder a otro hostname y puede permitir ataques man-in-the-middle.
+
+No resuelvas un problema de certificado en producción desactivando globalmente la verificación.
+
+## 50. PKI privada debe usar un CA bundle explícito
+
+Requests permite que `verify` apunte a un CA bundle confiable:
+
+```python
+response = requests.get(
+ url,
+ verify="/path/to/company-ca-bundle.pem",
+ timeout=(3, 10),
+)
+```
+
+Requests también reconoce `REQUESTS_CA_BUNDLE`, con `CURL_CA_BUNDLE` como fallback en su comportamiento documentado de entorno.
+
+## 51. Los certificados de cliente soportan flujos de mTLS
+
+El parámetro `cert` puede apuntar a un certificado de cliente o a un par certificado/clave:
+
+```python
+response = requests.get(
+ url,
+ cert=("client.crt", "client.key"),
+ timeout=(3, 10),
+)
+```
+
+Los archivos de clave privada son secretos. Protégelos como credenciales.
+
+## 52. Los proxies pueden provenir de argumentos o del entorno
+
+Requests soporta configuración de proxy por solicitud y settings derivados del entorno.
+
+```python
+proxies = {"https": "http://proxy.example:8080"}
+response = requests.get(url, proxies=proxies, timeout=(3, 10))
+```
+
+No coloques credenciales reales de proxy en el código fuente.
+
+## 53. `Session.trust_env` controla la integración con el entorno
+
+Las Sessions confían por defecto en configuración relevante del entorno, como proxies y fuentes de autenticación.
+
+Si un cliente debe operar independientemente de la configuración ambiente del proceso, evalúa `session.trust_env` explícitamente y documenta las consecuencias.
+
+## 54. Streaming evita cargar el cuerpo completo de inmediato
+
+```python
+with requests.get(url, stream=True, timeout=(3, 30)) as response:
+ response.raise_for_status()
+ for chunk in response.iter_content(chunk_size=64 * 1024):
+ if chunk:
+ process(chunk)
+```
+
+Streaming es útil para descargas grandes y procesamiento incremental.
+
+## 55. Las respuestas en streaming deben consumirse o cerrarse
+
+La reutilización de conexión depende de liberar la conexión subyacente.
+
+Usar la respuesta como context manager hace explícito el límite de propiedad incluso si el procesamiento lanza una excepción.
+
+## 56. `iter_content()` suele ser mejor que leer `raw` directamente
+
+`iter_content()` coopera con el comportamiento de decodificación de Requests y con la iteración por chunks.
+
+Elige el tamaño del chunk según el caso de uso en vez de asumir que cada chunk coincide con un mensaje lógico del servidor.
+
+## 57. Streaming por líneas es útil para protocolos orientados a líneas
+
+`response.iter_lines()` puede procesar una respuesta de streaming línea por línea.
+
+Ten cuidado con líneas vacías de keep-alive, registros parciales y semántica de reconexión definida por la API concreta.
+
+## 58. Escribe descargas grandes de forma segura
+
+Para un artefacto importante, un patrón más seguro es:
+
+```text
+download to temporary path
+-> verify status / size / checksum if available
+-> flush and close
+-> atomically move into final location
+```
+
+Esto evita que una descarga parcial se haga pasar por un archivo completo.
+
+## 59. Los uploads multipart usan `files=`
+
+```python
+with open("report.txt", "rb") as file_handle:
+ response = requests.post(
+ url,
+ files={"file": ("report.txt", file_handle, "text/plain")},
+ timeout=(3, 30),
+ )
+```
+
+El archivo debe abrirse en modo binario para un manejo predecible de bytes.
+
+## 60. Requests no reintenta conexiones fallidas por defecto
+
+El `HTTPAdapter` incorporado tiene cero retries de conexión por defecto.
+
+Los retries son una política de aplicación, no algo que debas asumir que ocurrió automáticamente.
+
+## 61. `HTTPAdapter` puede añadir una política de retry
+
+Para comportamiento granular, la documentación de Requests usa la clase `Retry` de urllib3:
+
+```python
+from requests import Session
+from requests.adapters import HTTPAdapter
+from urllib3.util import Retry
+
+
+retry_policy = Retry(
+ total=3,
+ backoff_factor=0.5,
+ status_forcelist=[429, 502, 503, 504],
+ allowed_methods={"GET", "HEAD"},
+)
+
+with Session() as session:
+ session.mount("https://", HTTPAdapter(max_retries=retry_policy))
+ response = session.get(url, timeout=(3, 10))
+```
+
+La política de retry debe diseñarse junto al contrato de API, no copiarse a ciegas.
+
+## 62. Nunca reintentes una escritura solo porque falló
+
+Un timeout después de enviar un POST puede significar:
+
+```text
+client does not know whether server committed the operation
+```
+
+Repetirlo ciegamente puede crear duplicados.
+
+Usa idempotency keys, identificadores de operación, métodos seguros o lógica de reconciliación cuando el servicio lo permita.
+
+## 63. Respeta `Retry-After` y los contratos de rate limit
+
+Una respuesta `429 Too Many Requests` normalmente indica que el cliente debe bajar el ritmo.
+
+Los headers exactos y el comportamiento de retry dependen de la API. Lee el contrato del servicio y evita loops cerrados que amplifiquen una caída.
+
+## 64. Backoff reduce tormentas de retry
+
+Los retries normalmente deben esperar entre intentos. Backoff exponencial y jitter ayudan a que muchos clientes no sincronicen sus reintentos contra el mismo servicio en recuperación.
+
+La política exacta pertenece al diseño de confiabilidad del sistema.
+
+## 65. Los response hooks pueden añadir comportamiento transversal
+
+Requests soporta hooks de `response`:
+
+```python
+def record_status(response: requests.Response, *args: object, **kwargs: object) -> None:
+ print(response.status_code)
+
+
+response = requests.get(
+ url,
+ hooks={"response": record_status},
+ timeout=(3, 10),
+)
+```
+
+Los hooks deben permanecer pequeños y manejar sus propias suposiciones y fallas.
+
+## 66. La paginación es un contrato de API, no una función de Requests
+
+Las APIs pueden paginar mediante:
+
+```text
+page/limit query parameters
+cursor tokens
+Link headers
+next URLs in JSON
+```
+
+Tu cliente debe detenerse en la condición terminal documentada por el servicio y protegerse de loops infinitos accidentales.
+
+## 67. Los Link headers ya se analizan
+
+Cuando una respuesta contiene headers estándar de Web Linking, Requests expone los links analizados mediante:
+
+```python
+next_link = response.links.get("next")
+```
+
+No asumas que todas las APIs usan Link headers para paginación.
+
+## 68. Valida el media type de la respuesta cuando sea importante
+
+Si un endpoint promete JSON, inspecciona el contrato cuando corresponda:
+
+```python
+content_type = response.headers.get("Content-Type", "")
+if "application/json" not in content_type.lower():
+ raise ValueError("Expected a JSON response")
+```
+
+Sé lo suficientemente preciso para la API integrada; los media types pueden incluir parámetros o formas vendor-specific `+json`.
+
+## 69. Registrar llamadas HTTP exige redactar datos sensibles
+
+Un registro estructurado útil puede contener:
+
+```text
+service name
+operation
+HTTP method
+sanitized route
+status code
+elapsed time
+retry attempt
+correlation ID
+```
+
+Evita registrar URLs completas cuando los parámetros de query puedan incluir secretos o datos personales.
+
+## 70. Las pruebas deterministas no deben depender de internet pública
+
+Un endpoint público puede ser lento, estar caído, limitado por rate limit, bloqueado geográficamente o cambiar sin relación con tu repositorio.
+
+Los ejemplos ejecutables de este capítulo inician un `ThreadingHTTPServer` local en `127.0.0.1`, ejercitan Requests contra él y luego lo apagan. Así se prueba HTTP real sin dependencia externa de red.
+
+## 71. Ejemplo práctico: GET con parámetros de query
+
+[`examples/get_with_query.py`](examples/get_with_query.py) envía un GET HTTP local real, deja que Requests codifique parámetros de query, verifica el status, decodifica JSON e imprime el contrato de query analizado.
+
+Salida esperada:
+
+```text
+status: 200
+path: /items
+query: {'status': ['open'], 'limit': ['2']}
+```
+
+## 72. Ejemplo práctico: POST JSON
+
+[`examples/post_json.py`](examples/post_json.py) verifica que `json=` envía un payload JSON con el content type esperado.
+
+Salida esperada:
+
+```text
+status: 201
+created: {'name': 'Nova', 'active': True}
+content-type: application/json
+```
+
+## 73. Ejemplo práctico: defaults de Session
+
+[`examples/session_defaults.py`](examples/session_defaults.py) usa una Session para aplicar headers compartidos y confirma que el servidor local los recibió.
+
+Salida esperada:
+
+```text
+client: python-study-guide
+auth-scheme: Bearer
+```
+
+El token es ficticio y existe solo dentro del proceso local del ejemplo.
+
+## 74. Ejemplo práctico: errores HTTP visibles
+
+[`examples/http_error_handling.py`](examples/http_error_handling.py) recibe `404` intencionalmente y demuestra que `raise_for_status()` conserva la respuesta en `HTTPError`.
+
+Salida esperada:
+
+```text
+caught: HTTPError
+status: 404
+```
+
+## 75. Ejemplo práctico: descarga por streaming
+
+[`examples/stream_download.py`](examples/stream_download.py) transmite bytes locales deterministas con `iter_content()` y cierra la respuesta mediante context manager.
+
+Salida esperada:
+
+```text
+bytes: 12
+content: chunked-data
+```
+
+## 76. Errores comunes
+
+Evita estos patrones:
+
+| Error | Por qué es riesgoso | Mejor enfoque |
+|---|---|---|
+| omitir `timeout` | la solicitud puede esperar indefinidamente | definir expectativas de conexión/lectura |
+| llamar `json()` y asumir éxito | respuestas de error pueden contener JSON válido | verificar status HTTP y contrato del payload |
+| usar `verify=False` en producción | desactiva verificación de identidad TLS | reparar la cadena de confianza o proporcionar CA bundle |
+| reintentar cualquier excepción | puede duplicar escrituras o empeorar caídas | reintentar solo fallas seguras y clasificadas |
+| crear una Session nueva para cada llamada | pierde reutilización de conexión | mantener Session durante la vida lógica del cliente |
+| registrar Authorization | filtra credenciales | redactar secretos |
+| confiar en forma JSON arbitraria | drift de schema se vuelve bug oculto | validar campos/tipos requeridos |
+| probar contra API pública de demostración | CI se vuelve externamente frágil | usar servidor local o test double controlado |
+
+## 77. Tabla de decisión
+
+| Necesidad | Prefiere |
+|---|---|
+| una solicitud simple | `requests.get/post/...` de nivel superior |
+| llamadas repetidas a un servicio | `requests.Session()` |
+| cuerpo JSON | `json=` |
+| cuerpo de formulario | `data=` |
+| parámetros de query | `params=` |
+| respuesta grande | `stream=True` + `iter_content()` |
+| una falla HTTP debe detener el flujo | `raise_for_status()` |
+| retry personalizado | `HTTPAdapter` + política explícita de `Retry` |
+| CA privada | `verify=` |
+| prueba determinista en CI | servidor HTTP local / test double controlado |
+
+## 78. Referencia rápida
+
+```python
+import requests
+
+
+with requests.Session() as session:
+ response = session.get(
+ "https://example.com/api/items",
+ params={"limit": 20},
+ headers={"Accept": "application/json"},
+ timeout=(3, 10),
+ )
+ response.raise_for_status()
+ data = response.json()
+```
+
+Antes de producción, añade las reglas específicas de autenticación, validación de schema, observabilidad, paginación, retry y seguridad que requiera tu integración.
+
+## 79. Checklist de diseño de cliente HTTP
+
+Antes de publicar una integración, responde:
+
+1. ¿Qué métodos HTTP y rutas están permitidos?
+2. ¿De dónde provienen las base URLs?
+3. ¿Todas las URLs externas son confiables o validadas?
+4. ¿Cuáles son los connect y read timeouts?
+5. ¿Qué status codes se esperan?
+6. ¿Qué media types y campos de respuesta son obligatorios?
+7. ¿Cómo se obtienen, rotan y redactan las credenciales?
+8. ¿La verificación TLS está habilitada y la PKI privada está correctamente configurada?
+9. ¿El cliente reutiliza una Session?
+10. ¿Qué fallas son retryable?
+11. ¿Las escrituras repetidas son idempotentes o están protegidas por idempotency keys?
+12. ¿Cómo se manejan paginación y rate limits?
+13. ¿Las respuestas en streaming siempre se cierran?
+14. ¿Qué telemetría es segura de registrar?
+15. ¿Las pruebas pueden ejecutarse sin depender de una red pública?
+
+## 80. Ejercicio integrado
+
+Construye un **cliente ficticio de API de inventario** contra un servidor HTTP local de prueba.
+
+Requisitos:
+
+1. Crea una clase reutilizable `InventoryClient` que posea una `requests.Session`.
+2. Acepta una base URL en el constructor.
+3. Aplica un header `Accept: application/json` a nivel de Session.
+4. Añade un método que liste items con parámetros `status` y `limit`.
+5. Añade un método que cree un item con cuerpo JSON.
+6. Usa timeouts explícitos de conexión/lectura.
+7. Llama `raise_for_status()` antes de decodificar un payload de éxito.
+8. Valida que los objetos item contengan un `id` entero y un `name` string.
+9. Traduce excepciones de Requests a una excepción propia de aplicación preservando la original con `raise ... from`.
+10. Nunca registres el valor de Authorization.
+11. Añade un servidor local que devuelva casos 200, 201, 404, 429 y JSON malformado.
+12. Prueba que una respuesta `204` no se decodifique como JSON.
+13. Añade paginación con una condición terminal documentada.
+14. Explica qué operaciones reintentarías y por qué.
+15. Añade un desafío con idempotency key para una escritura.
+
+Desafíos de extensión:
+
+- transmitir un export generado a un archivo temporal y renombrarlo atómicamente;
+- configurar una política segura de retry para GET con backoff;
+- añadir logs de tiempo de respuesta con correlation ID;
+- usar una prepared request para inspeccionar los headers exactos antes de enviar;
+- definir un pequeño modelo tipado después de validar el contrato JSON.
+
+## 81. Conexiones con conceptos anteriores
+
+`requests` se apoya directamente en material anterior:
+
+- **diccionarios:** headers, query params, cookies y objetos JSON;
+- **funciones/clases:** clientes reutilizables y fronteras explícitas;
+- **excepciones:** fallas de transporte, HTTP y decodificación;
+- **JSON:** serialización y decodificación de payloads;
+- **`pathlib`:** destinos seguros de descarga y rutas de CA/certificados;
+- **logging:** llamadas observables con redacción de secretos;
+- **`datetime`:** timestamps, validators de caché y campos de fecha en APIs;
+- **`os`:** variables de entorno para configuración en runtime;
+- **`pandas`:** transformar datos tabulares recibidos desde una API;
+- **`openpyxl`:** convertir datos de API en workbooks Excel controlados.
+
+## 82. Referencias primarias
+
+- [Documentación de Requests](https://requests.readthedocs.io/en/latest/)
+- [Requests Quickstart](https://requests.readthedocs.io/en/latest/user/quickstart/)
+- [Requests Advanced Usage](https://requests.readthedocs.io/en/latest/user/advanced/)
+- [Requests Developer Interface](https://requests.readthedocs.io/en/latest/api/)
+- [Requests en PyPI](https://pypi.org/project/requests/)
+- [Historial de releases de Requests](https://github.com/psf/requests/releases)
+
+Cuando se preparó este capítulo, Requests 2.34.2 era la versión estable más reciente. El currículo apunta a la serie 2.34.x en lugar de depender de una versión futura sin límite.
+
+## 83. Próximo capítulo
+
+La Fase 9 ahora conecta tres fronteras prácticas:
+
+```text
+pandas -> transform tabular data
+openpyxl -> construct and maintain Excel workbooks
+requests -> exchange data with HTTP services and APIs
+```
+
+La siguiente biblioteca planificada es **`pytest`**, donde el enfoque pasa de usar bibliotecas externas a demostrar sistemáticamente que el comportamiento de Python continúa siendo correcto.
+
+Antes de continuar, construye al menos un cliente contra un servidor HTTP local. El código de API confiable comienza cuando el comportamiento de red se convierte en algo que puedes reproducir, inspeccionar y probar.
diff --git a/external-libraries/03-requests/README.md b/external-libraries/03-requests/README.md
new file mode 100644
index 0000000..f91fbcb
--- /dev/null
+++ b/external-libraries/03-requests/README.md
@@ -0,0 +1,998 @@
+
+
+# Consuming HTTP APIs with `requests`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Back to External Libraries](../README.md) · [← Previous: `openpyxl`](../02-openpyxl/README.md)
+
+Local files are only one kind of boundary. Modern Python programs also exchange data with web services, internal APIs, SaaS platforms, authentication servers, storage endpoints, and other systems over HTTP. The `requests` package gives Python a compact, readable HTTP client while still exposing the protocol concepts that reliable integrations need.
+
+This chapter targets **Requests 2.34.x** and was researched against the current **Requests 2.34.2** documentation and release metadata. Requests 2.34.2 requires Python 3.10 or newer; this repository validates examples on Python 3.13.
+
+**Estimated study time:** 270–360 minutes.
+
+## Learning goals
+
+By the end of this chapter, you should be able to:
+
+- explain the HTTP request/response model without treating an API call as a magical function call;
+- issue GET, POST, PUT, PATCH, and DELETE requests intentionally;
+- send query parameters, headers, form data, JSON bodies, files, and authentication data;
+- distinguish transport success, HTTP success, and payload validity;
+- configure connect/read timeouts and understand what they do not guarantee;
+- handle Requests exceptions without hiding useful failure context;
+- use `Session` for connection reuse, cookies, and shared request defaults;
+- understand redirects, TLS verification, CA bundles, proxies, and environment settings;
+- stream large responses safely and close resources deterministically;
+- add retries only when the operation is safe to repeat;
+- protect credentials and avoid logging secrets;
+- validate API response contracts instead of trusting arbitrary JSON;
+- build deterministic HTTP tests without depending on a public internet service.
+
+## 1. Why `requests` exists
+
+Python's standard library can speak HTTP, but `requests` provides a higher-level interface for common client work: URLs, query parameters, headers, cookies, authentication, request bodies, TLS verification, sessions, streaming, and exceptions.
+
+The convenience is valuable, but the network is still a distributed-system boundary. A readable API does not remove latency, partial failure, authentication rules, server errors, retries, or security decisions.
+
+## 2. Think in requests and responses
+
+A client sends an HTTP request containing some combination of:
+
+```text
+method + URL + headers + optional body
+```
+
+The server returns an HTTP response containing:
+
+```text
+status code + headers + body
+```
+
+Your Python code must reason about both halves.
+
+## 3. External libraries need a version contract
+
+This repository declares Phase 9 dependencies in `requirements-external.txt`.
+
+For this chapter the contract is:
+
+```text
+requests >= 2.34 and < 2.35
+```
+
+The 2.34 series also introduced inline typing in Requests itself, so current type checkers can consume public API types without depending on a separate stubs package.
+
+## 4. Install the repository dependency set
+
+Create and activate a virtual environment, then install:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+For isolated experimentation, `python -m pip install requests` is valid, but a project should record its supported dependency range.
+
+## 5. Import the package
+
+The conventional import is:
+
+```python
+import requests
+```
+
+The top-level module exposes convenience functions such as `get()`, `post()`, and `delete()`, plus classes such as `Session` and exception types under `requests.exceptions`.
+
+## 6. A URL is part of your input contract
+
+An HTTP URL commonly contains:
+
+```text
+scheme://host:port/path?query#fragment
+```
+
+For an HTTP client, the scheme, host, port, path, and query parameters affect the actual request. Fragments are generally interpreted client-side and are not sent as the HTTP request target.
+
+Do not concatenate untrusted URL fragments carelessly.
+
+## 7. Start with a GET request
+
+A basic GET looks like this:
+
+```python
+import requests
+
+
+response = requests.get("https://example.com/api/items", timeout=(3, 10))
+print(response.status_code)
+```
+
+This snippet is illustrative because it depends on an external endpoint. Published executable examples later in the chapter use a local test server instead.
+
+## 8. Status codes communicate HTTP outcomes
+
+Common classes are:
+
+```text
+1xx -> informational
+2xx -> successful response
+3xx -> redirection
+4xx -> client-side request problem
+5xx -> server-side failure
+```
+
+A `200` is not the only successful status. A POST may correctly return `201 Created`, and a DELETE may return `204 No Content`.
+
+## 9. Use `raise_for_status()` when non-success HTTP codes are failures
+
+```python
+response = requests.get(url, timeout=(3, 10))
+response.raise_for_status()
+```
+
+For unsuccessful HTTP status codes, `raise_for_status()` raises `requests.HTTPError` and keeps the response attached to the exception.
+
+Do not throw away that context when reporting a failure.
+
+## 10. Transport success is not application success
+
+An HTTP exchange can succeed at the network layer while the server returns `404`, `429`, or `500`.
+
+Conversely, a `200` response may contain data that violates your business contract.
+
+A robust client checks more than “did `requests.get()` return?”
+
+## 11. `response.text` is decoded text
+
+```python
+text = response.text
+```
+
+Requests decodes response bytes according to response encoding information and its own detection rules.
+
+Use text when the body is genuinely textual and you want a `str`.
+
+## 12. `response.content` gives raw bytes
+
+```python
+payload = response.content
+```
+
+Use bytes for binary files, checksums, image data, compressed artifacts, or any format where decoding to text would be incorrect.
+
+## 13. Decode JSON with `response.json()`
+
+```python
+response = requests.get(url, timeout=(3, 10))
+response.raise_for_status()
+data = response.json()
+```
+
+`response.json()` parses the response body. It does not prove that the HTTP status was successful.
+
+## 14. Invalid JSON has its own failure mode
+
+Requests exposes `requests.exceptions.JSONDecodeError` for JSON decoding failures.
+
+```python
+try:
+ data = response.json()
+except requests.exceptions.JSONDecodeError as exc:
+ raise RuntimeError("API returned invalid JSON") from exc
+```
+
+A `204 No Content` response, an HTML error page, or malformed JSON can make decoding fail.
+
+## 15. JSON shape still needs validation
+
+Even valid JSON can be wrong for your program:
+
+```python
+if not isinstance(data, dict) or "items" not in data:
+ raise ValueError("Unexpected API response shape")
+```
+
+Parsing answers “is this JSON?” Contract validation answers “is this the JSON our application expects?”
+
+## 16. Send query parameters with `params`
+
+Do not hand-build query strings when Requests can encode them:
+
+```python
+response = requests.get(
+ url,
+ params={"status": "open", "limit": 20},
+ timeout=(3, 10),
+)
+```
+
+Requests handles query encoding and exposes the final URL through `response.url`.
+
+## 17. Repeated query parameters may use sequences
+
+APIs sometimes expect repeated keys. Requests accepts sequence values or a list of two-tuples.
+
+```python
+params = [("tag", "python"), ("tag", "http")]
+response = requests.get(url, params=params, timeout=(3, 10))
+```
+
+Follow the target API's documented parameter contract.
+
+## 18. Request headers are metadata
+
+```python
+headers = {
+ "Accept": "application/json",
+ "User-Agent": "study-client/1.0",
+}
+response = requests.get(url, headers=headers, timeout=(3, 10))
+```
+
+Headers can communicate representation preferences, authentication, conditional requests, tracing, and other protocol metadata.
+
+## 19. Do not confuse request and response headers
+
+Request headers are what your client sends. Response headers are what the server returns.
+
+```python
+content_type = response.headers.get("Content-Type")
+```
+
+Requests response headers behave like a case-insensitive mapping.
+
+## 20. Use a descriptive `User-Agent` when appropriate
+
+Many APIs appreciate a client identifier for operations and support diagnostics.
+
+Avoid pretending to be an unrelated browser unless the integration contract explicitly requires it.
+
+## 21. POST form data with `data=`
+
+For form-style data:
+
+```python
+response = requests.post(
+ url,
+ data={"username": "demo", "mode": "compact"},
+ timeout=(3, 10),
+)
+```
+
+Requests encodes a mapping passed through `data=` as form data.
+
+## 22. POST JSON with `json=`
+
+For JSON APIs, prefer the dedicated parameter:
+
+```python
+payload = {"name": "Nova", "active": True}
+response = requests.post(url, json=payload, timeout=(3, 10))
+response.raise_for_status()
+```
+
+`json=` serializes the value and sets an appropriate JSON content type.
+
+## 23. Do not manually serialize JSON without a reason
+
+This is usually less clear:
+
+```python
+import json
+
+body = json.dumps(payload)
+response = requests.post(
+ url,
+ data=body,
+ headers={"Content-Type": "application/json"},
+ timeout=(3, 10),
+)
+```
+
+Use `json=` unless you need precise control over the serialized bytes.
+
+## 24. PUT and PATCH express different update contracts
+
+The HTTP specification and an API's own documentation determine semantics. Commonly:
+
+```text
+PUT -> replace or set a representation at a target
+PATCH -> partially modify a representation
+```
+
+Do not infer server behavior from method names alone. Read the API contract.
+
+## 25. DELETE may succeed without a body
+
+```python
+response = requests.delete(url, timeout=(3, 10))
+response.raise_for_status()
+```
+
+A successful `204 No Content` should not be followed by unconditional `response.json()`.
+
+## 26. Idempotency matters before retries
+
+An operation is idempotent when repeating the same intended request has the same intended effect as performing it once.
+
+GET, HEAD, PUT, and DELETE are defined with idempotent semantics at the HTTP method level; POST generally is not. Application behavior and idempotency keys can add further guarantees.
+
+## 27. Nearly all production requests need a timeout
+
+Requests does **not** time out by default.
+
+```python
+response = requests.get(url, timeout=10)
+```
+
+Without an explicit timeout, a program may wait indefinitely for network activity.
+
+## 28. Separate connect and read timeouts when useful
+
+Requests accepts a tuple:
+
+```python
+response = requests.get(url, timeout=(3, 15))
+```
+
+The first value is the connect timeout. The second is the read timeout.
+
+This is often clearer than one number for integrations where establishing a connection and waiting for response bytes have different expectations.
+
+## 29. A Requests timeout is not a total wall-clock deadline
+
+The documented timeout behavior is based on socket inactivity, not a guaranteed maximum duration for the complete download.
+
+A slow response that keeps delivering bytes may last longer than the nominal read-timeout value.
+
+If a workflow needs a hard overall deadline, design that deadline separately.
+
+## 30. Requests has a useful exception hierarchy
+
+Requests-specific exceptions inherit from `requests.exceptions.RequestException`.
+
+Important subclasses include:
+
+```text
+HTTPError
+ConnectionError
+Timeout
+TooManyRedirects
+JSONDecodeError
+SSLError
+```
+
+Catch narrowly when you have distinct recovery behavior.
+
+## 31. `Timeout` deserves explicit handling
+
+```python
+try:
+ response = requests.get(url, timeout=(3, 10))
+except requests.Timeout as exc:
+ raise RuntimeError("Remote service timed out") from exc
+```
+
+A timeout is different from an HTTP `500`: the client may not know whether the server processed the request.
+
+That uncertainty is crucial before retrying a write operation.
+
+## 32. `ConnectionError` covers network connection failures
+
+DNS resolution failures, refused connections, and related transport failures may surface as `requests.ConnectionError`.
+
+Do not turn these into an empty successful result. Preserve the failure signal or apply an explicit recovery policy.
+
+## 33. `HTTPError` gives access to the response
+
+```python
+try:
+ response.raise_for_status()
+except requests.HTTPError as exc:
+ status = exc.response.status_code
+ raise RuntimeError(f"API returned HTTP {status}") from exc
+```
+
+Avoid including arbitrary response bodies in logs because they may contain secrets or personal data.
+
+## 34. A top-level `RequestException` boundary can add context
+
+At an application boundary, a wrapper can add the service operation name:
+
+```python
+try:
+ response = requests.get(url, timeout=(3, 10))
+ response.raise_for_status()
+except requests.RequestException as exc:
+ raise RuntimeError("Could not load catalog") from exc
+```
+
+Do not catch `Exception` merely to make a network failure disappear.
+
+## 35. Redirects have history
+
+Requests follows redirects for common GET-style requests and exposes prior responses through:
+
+```python
+for previous in response.history:
+ print(previous.status_code, previous.url)
+```
+
+The final response URL is available as `response.url`.
+
+## 36. Limit or disable redirects when the contract requires it
+
+```python
+response = requests.get(
+ url,
+ allow_redirects=False,
+ timeout=(3, 10),
+)
+```
+
+Redirect behavior can matter for authentication, auditing, SSRF defenses, and signed URLs.
+
+## 37. Basic authentication has a dedicated helper
+
+```python
+from requests.auth import HTTPBasicAuth
+
+
+response = requests.get(
+ url,
+ auth=HTTPBasicAuth("demo-user", "demo-password"),
+ timeout=(3, 10),
+)
+```
+
+Never hard-code real credentials in source code, examples, commits, or logs.
+
+## 38. Bearer tokens are usually headers
+
+```python
+headers = {"Authorization": f"Bearer {token}"}
+response = requests.get(url, headers=headers, timeout=(3, 10))
+```
+
+The token should come from a secure runtime source such as a secret manager or protected environment variable, not from a repository file.
+
+## 39. Redact secrets from observability
+
+Do not log full values of:
+
+```text
+Authorization
+Proxy-Authorization
+Cookie
+Set-Cookie
+API keys
+signed URLs
+client certificates or private keys
+```
+
+Useful logging can include the HTTP method, sanitized host/path, status code, elapsed time, retry count, and correlation identifiers.
+
+## 40. `Session` keeps state across requests
+
+```python
+import requests
+
+
+with requests.Session() as session:
+ session.headers.update({"Accept": "application/json"})
+ response = session.get(url, timeout=(3, 10))
+```
+
+A Session can persist defaults and cookies across calls.
+
+## 41. Sessions also reuse connections
+
+Requests Sessions use urllib3 connection pooling. Multiple calls to the same host can reuse underlying connections instead of establishing a new TCP/TLS connection every time.
+
+This can materially reduce overhead in repeated API workflows.
+
+## 42. Session defaults can be overridden per request
+
+Session-level headers, authentication, cookies, proxies, and other parameters are convenient defaults, not immutable globals.
+
+Keep shared configuration intentional so one request does not accidentally inherit credentials intended for another service.
+
+## 43. Close Sessions deterministically
+
+Use a context manager or call `close()`:
+
+```python
+with requests.Session() as session:
+ response = session.get(url, timeout=(3, 10))
+ response.raise_for_status()
+```
+
+Resource ownership should be visible in long-running processes.
+
+## 44. Cookies can persist in a Session
+
+A server may set cookies in one response and expect them in later requests. A Session maintains a cookie jar across calls.
+
+For API clients, explicit token-based authentication is often easier to reason about, but cookie-based workflows still exist.
+
+## 45. Prepared requests expose the exact outgoing request
+
+Requests can build a `PreparedRequest` before sending it:
+
+```python
+from requests import Request, Session
+
+
+with Session() as session:
+ request = Request("GET", url, headers={"X-Trace": "demo"})
+ prepared = session.prepare_request(request)
+ print(prepared.method, prepared.url)
+```
+
+This is useful for advanced signing, inspection, or controlled request mutation.
+
+## 46. Prefer `Session.prepare_request()` when Session state matters
+
+Calling `Request.prepare()` directly does not automatically apply all Session-level state.
+
+If cookies, default headers, or authentication from a Session are part of the contract, prepare the request through that Session.
+
+## 47. Prepared-request flows need environment awareness
+
+The advanced Requests documentation notes that manually sending a prepared request can bypass environment-derived settings unless they are merged explicitly.
+
+This matters for settings such as CA bundles and proxies.
+
+Advanced request preparation should therefore be deliberate, not a default pattern for ordinary calls.
+
+## 48. HTTPS certificate verification is enabled by default
+
+Requests verifies server TLS certificates for HTTPS connections.
+
+```python
+response = requests.get("https://example.com", timeout=(3, 10))
+```
+
+If certificate verification fails, Requests raises `SSLError` rather than silently trusting the peer.
+
+## 49. `verify=False` disables an important security guarantee
+
+```python
+response = requests.get(url, verify=False, timeout=(3, 10))
+```
+
+This accepts certificates that may be expired, self-signed, or for the wrong hostname and can enable man-in-the-middle attacks.
+
+Do not solve a production certificate problem by globally disabling verification.
+
+## 50. Private PKI should use an explicit CA bundle
+
+Requests lets `verify` point to a trusted CA bundle:
+
+```python
+response = requests.get(
+ url,
+ verify="/path/to/company-ca-bundle.pem",
+ timeout=(3, 10),
+)
+```
+
+Requests also recognizes `REQUESTS_CA_BUNDLE`, with `CURL_CA_BUNDLE` as a fallback in its documented environment behavior.
+
+## 51. Client certificates support mutual TLS workflows
+
+The `cert` parameter can point to a client certificate or a certificate/key pair:
+
+```python
+response = requests.get(
+ url,
+ cert=("client.crt", "client.key"),
+ timeout=(3, 10),
+)
+```
+
+Private-key files are secrets. Protect them as credentials.
+
+## 52. Proxies may come from arguments or the environment
+
+Requests supports per-request proxy configuration and environment-derived proxy settings.
+
+```python
+proxies = {"https": "http://proxy.example:8080"}
+response = requests.get(url, proxies=proxies, timeout=(3, 10))
+```
+
+Do not place real proxy credentials in source code.
+
+## 53. `Session.trust_env` controls environment integration
+
+Sessions default to trusting relevant environment configuration such as proxies and authentication sources.
+
+If a client must operate independently of ambient process configuration, evaluate `session.trust_env` explicitly and document the consequences.
+
+## 54. Streaming avoids loading an entire body immediately
+
+```python
+with requests.get(url, stream=True, timeout=(3, 30)) as response:
+ response.raise_for_status()
+ for chunk in response.iter_content(chunk_size=64 * 1024):
+ if chunk:
+ process(chunk)
+```
+
+Streaming is useful for large downloads and incremental processing.
+
+## 55. Streamed responses must be consumed or closed
+
+Connection reuse depends on releasing the underlying connection.
+
+Using the response as a context manager makes the ownership boundary explicit even when processing raises an exception.
+
+## 56. `iter_content()` is usually better than reading `raw` directly
+
+`iter_content()` cooperates with Requests' decoding behavior and chunked iteration.
+
+Choose a chunk size based on the use case rather than assuming each chunk corresponds to a server-side message boundary.
+
+## 57. Streaming lines is useful for line-oriented protocols
+
+`response.iter_lines()` can process a streaming response line by line.
+
+Be careful with keep-alive blank lines, partial application records, and reconnect semantics defined by the specific streaming API.
+
+## 58. Write large downloads safely
+
+For an important artifact, a safer pattern is:
+
+```text
+download to temporary path
+-> verify status / size / checksum if available
+-> flush and close
+-> atomically move into final location
+```
+
+This prevents a partial download from masquerading as a completed file.
+
+## 59. Multipart uploads use `files=`
+
+```python
+with open("report.txt", "rb") as file_handle:
+ response = requests.post(
+ url,
+ files={"file": ("report.txt", file_handle, "text/plain")},
+ timeout=(3, 30),
+ )
+```
+
+The file object should be opened in binary mode for predictable byte handling.
+
+## 60. Requests does not retry failed connections by default
+
+The built-in `HTTPAdapter` defaults to no connection retries.
+
+Retries are an application policy, not something to assume happened automatically.
+
+## 61. `HTTPAdapter` can add a retry policy
+
+For granular behavior, Requests documents using urllib3's `Retry` class:
+
+```python
+from requests import Session
+from requests.adapters import HTTPAdapter
+from urllib3.util import Retry
+
+
+retry_policy = Retry(
+ total=3,
+ backoff_factor=0.5,
+ status_forcelist=[429, 502, 503, 504],
+ allowed_methods={"GET", "HEAD"},
+)
+
+with Session() as session:
+ session.mount("https://", HTTPAdapter(max_retries=retry_policy))
+ response = session.get(url, timeout=(3, 10))
+```
+
+Retry policy should be designed with the API contract, not copied blindly.
+
+## 62. Never retry a write merely because it failed
+
+A timeout after sending a POST can mean:
+
+```text
+client does not know whether server committed the operation
+```
+
+Blindly repeating it may create duplicates.
+
+Use idempotency keys, operation identifiers, safe methods, or reconciliation logic when the service supports them.
+
+## 63. Respect `Retry-After` and rate-limit contracts
+
+A `429 Too Many Requests` response often means the client should slow down.
+
+The exact headers and retry behavior are API-specific. Read the service contract and avoid tight retry loops that amplify an outage.
+
+## 64. Backoff reduces retry storms
+
+Retries should normally wait between attempts. Exponential backoff and jitter help many clients avoid synchronizing their retries against the same recovering service.
+
+The exact policy belongs to your system reliability design.
+
+## 65. Response hooks can add cross-cutting behavior
+
+Requests supports `response` hooks:
+
+```python
+def record_status(response: requests.Response, *args: object, **kwargs: object) -> None:
+ print(response.status_code)
+
+
+response = requests.get(
+ url,
+ hooks={"response": record_status},
+ timeout=(3, 10),
+)
+```
+
+Hooks should remain small and must handle their own assumptions and failures.
+
+## 66. Pagination is an API contract, not a Requests feature
+
+APIs may paginate with:
+
+```text
+page/limit query parameters
+cursor tokens
+Link headers
+next URLs in JSON
+```
+
+Your client should stop on the service's documented terminal condition and defend against accidental infinite loops.
+
+## 67. Link headers are parsed for you
+
+When a response contains standard Web Linking headers, Requests exposes parsed links through:
+
+```python
+next_link = response.links.get("next")
+```
+
+Do not assume every API uses Link headers for pagination.
+
+## 68. Validate response media type when it matters
+
+If an endpoint promises JSON, inspect the response contract as needed:
+
+```python
+content_type = response.headers.get("Content-Type", "")
+if "application/json" not in content_type.lower():
+ raise ValueError("Expected a JSON response")
+```
+
+Be precise enough for the API you integrate with; media types may include parameters or vendor-specific `+json` forms.
+
+## 69. Logging HTTP calls requires redaction
+
+A useful structured record may contain:
+
+```text
+service name
+operation
+HTTP method
+sanitized route
+status code
+elapsed time
+retry attempt
+correlation ID
+```
+
+Avoid logging full URLs when query parameters may contain secrets or personal information.
+
+## 70. Deterministic tests should not depend on the public internet
+
+A public endpoint can be slow, unavailable, rate-limited, geo-blocked, or changed independently of your repository.
+
+The executable examples in this chapter start a local `ThreadingHTTPServer` on `127.0.0.1`, exercise Requests against it, then shut it down. That tests real HTTP behavior without external network dependence.
+
+## 71. Practical example: GET with query parameters
+
+[`examples/get_with_query.py`](examples/get_with_query.py) sends a real local HTTP GET request, lets Requests encode query parameters, checks the status, decodes JSON, and prints the parsed query contract.
+
+Expected output:
+
+```text
+status: 200
+path: /items
+query: {'status': ['open'], 'limit': ['2']}
+```
+
+## 72. Practical example: POST JSON
+
+[`examples/post_json.py`](examples/post_json.py) verifies that `json=` sends a JSON payload with the expected content type.
+
+Expected output:
+
+```text
+status: 201
+created: {'name': 'Nova', 'active': True}
+content-type: application/json
+```
+
+## 73. Practical example: Session defaults
+
+[`examples/session_defaults.py`](examples/session_defaults.py) uses a Session to apply shared headers, then confirms that the local server received them.
+
+Expected output:
+
+```text
+client: python-study-guide
+auth-scheme: Bearer
+```
+
+The token is fictional and exists only inside the local example process.
+
+## 74. Practical example: visible HTTP errors
+
+[`examples/http_error_handling.py`](examples/http_error_handling.py) intentionally receives `404` and demonstrates `raise_for_status()` preserving the response on `HTTPError`.
+
+Expected output:
+
+```text
+caught: HTTPError
+status: 404
+```
+
+## 75. Practical example: streaming download
+
+[`examples/stream_download.py`](examples/stream_download.py) streams deterministic local bytes with `iter_content()` and closes the response through a context manager.
+
+Expected output:
+
+```text
+bytes: 12
+content: chunked-data
+```
+
+## 76. Common mistakes
+
+Avoid these patterns:
+
+| Mistake | Why it is risky | Better approach |
+|---|---|---|
+| omit `timeout` | request may wait indefinitely | define connect/read expectations |
+| call `json()` and assume success | error responses can contain valid JSON | check HTTP status and payload contract |
+| use `verify=False` in production | disables TLS identity verification | fix trust chain or provide CA bundle |
+| retry every exception | may duplicate writes or worsen outages | retry only safe, classified failures |
+| create a new Session for every call | loses connection reuse | own a Session for a logical client lifetime |
+| log Authorization headers | leaks credentials | redact secrets |
+| trust arbitrary JSON shape | schema drift becomes hidden bugs | validate required fields/types |
+| test against a public demo API | CI becomes externally fragile | use a local server or controlled test double |
+
+## 77. Decision table
+
+| Need | Prefer |
+|---|---|
+| one simple request | top-level `requests.get/post/...` |
+| repeated calls to one service | `requests.Session()` |
+| JSON request body | `json=` |
+| form request body | `data=` |
+| query parameters | `params=` |
+| large response | `stream=True` + `iter_content()` |
+| HTTP failure should stop flow | `raise_for_status()` |
+| custom retry behavior | `HTTPAdapter` + explicit `Retry` policy |
+| private CA | `verify=` |
+| deterministic CI test | local HTTP server / controlled test double |
+
+## 78. Quick reference
+
+```python
+import requests
+
+
+with requests.Session() as session:
+ response = session.get(
+ "https://example.com/api/items",
+ params={"limit": 20},
+ headers={"Accept": "application/json"},
+ timeout=(3, 10),
+ )
+ response.raise_for_status()
+ data = response.json()
+```
+
+Before production, add the service-specific authentication, schema validation, observability, pagination, retry, and security rules your integration requires.
+
+## 79. HTTP client design checklist
+
+Before publishing an integration, answer:
+
+1. Which HTTP methods and routes are allowed?
+2. Where do base URLs come from?
+3. Are all external URLs trusted or validated?
+4. What are the connect and read timeouts?
+5. Which status codes are expected?
+6. Which response media types and fields are required?
+7. How are credentials sourced, rotated, and redacted?
+8. Is TLS verification enabled and is private PKI configured correctly?
+9. Does the client reuse a Session?
+10. Which failures are retryable?
+11. Are repeated write operations idempotent or protected by idempotency keys?
+12. How are pagination and rate limits handled?
+13. Are streamed responses always closed?
+14. What telemetry is safe to record?
+15. Can tests run without a public network dependency?
+
+## 80. Integrated exercise
+
+Build a fictional **inventory API client** against a local HTTP test server.
+
+Requirements:
+
+1. Create a reusable `InventoryClient` class that owns a `requests.Session`.
+2. Accept a base URL in the constructor.
+3. Apply an `Accept: application/json` header at Session level.
+4. Add a method that lists items with `status` and `limit` query parameters.
+5. Add a method that creates an item with a JSON body.
+6. Use explicit connect/read timeouts.
+7. Call `raise_for_status()` before decoding a success payload.
+8. Validate that item objects contain an integer `id` and string `name`.
+9. Translate Requests exceptions into one application-specific exception while preserving the original exception with `raise ... from`.
+10. Never log the Authorization value.
+11. Add a local test server that returns 200, 201, 404, 429, and malformed JSON cases.
+12. Test that a `204` response is not decoded as JSON.
+13. Add pagination with a documented terminal condition.
+14. Explain which operations you would retry and why.
+15. Add one extension challenge using an idempotency key for a write request.
+
+Extension challenges:
+
+- stream a generated export into a temporary file and atomically rename it;
+- configure a safe GET retry policy with backoff;
+- add response timing logs with a correlation ID;
+- use a prepared request to inspect the exact headers before sending;
+- define a small typed data model after validating the JSON contract.
+
+## 81. Connections to earlier concepts
+
+`requests` builds directly on earlier material:
+
+- **dictionaries:** headers, query parameters, cookies, and JSON objects;
+- **functions/classes:** reusable service clients and explicit boundaries;
+- **exceptions:** transport, HTTP, and decoding failures;
+- **JSON:** payload serialization and decoding;
+- **`pathlib`:** safe download destinations and CA/certificate paths;
+- **logging:** observable calls with secret redaction;
+- **`datetime`:** timestamps, cache validators, and API date fields;
+- **`os`:** environment variables for runtime configuration;
+- **`pandas`:** transform tabular data received from an API;
+- **`openpyxl`:** turn API data into controlled Excel workbooks.
+
+## 82. Primary references
+
+- [Requests documentation](https://requests.readthedocs.io/en/latest/)
+- [Requests Quickstart](https://requests.readthedocs.io/en/latest/user/quickstart/)
+- [Requests Advanced Usage](https://requests.readthedocs.io/en/latest/user/advanced/)
+- [Requests Developer Interface](https://requests.readthedocs.io/en/latest/api/)
+- [Requests on PyPI](https://pypi.org/project/requests/)
+- [Requests release history](https://github.com/psf/requests/releases)
+
+At the time this chapter was prepared, Requests 2.34.2 was the latest stable release. The curriculum targets the 2.34.x series instead of relying on an unbounded future version.
+
+## 83. Next chapter
+
+Phase 9 now connects three practical boundaries:
+
+```text
+pandas -> transform tabular data
+openpyxl -> construct and maintain Excel workbooks
+requests -> exchange data with HTTP services and APIs
+```
+
+The next planned library is **`pytest`**, where the focus moves from using external libraries to systematically proving that Python behavior remains correct.
+
+Before moving on, build at least one client against a local HTTP server. Reliable API code starts when network behavior becomes something you can reproduce, inspect, and test.
diff --git a/external-libraries/03-requests/README.pt-BR.md b/external-libraries/03-requests/README.pt-BR.md
new file mode 100644
index 0000000..71dc19c
--- /dev/null
+++ b/external-libraries/03-requests/README.pt-BR.md
@@ -0,0 +1,998 @@
+
+
+# Consumindo APIs HTTP com `requests`
+
+[🇺🇸 English](README.md) · [🇧🇷 Português](README.pt-BR.md) · [🇪🇸 Español](README.es.md)
+
+
+
+[← Voltar para Bibliotecas Externas](../README.pt-BR.md) · [← Anterior: `openpyxl`](../02-openpyxl/README.pt-BR.md)
+
+Arquivos locais são apenas um tipo de fronteira. Programas Python modernos também trocam dados com serviços web, APIs internas, plataformas SaaS, servidores de autenticação, endpoints de armazenamento e outros sistemas por HTTP. O pacote `requests` oferece ao Python um cliente HTTP compacto e legível, mantendo visíveis os conceitos de protocolo necessários para integrações confiáveis.
+
+Este capítulo mira a série **Requests 2.34.x** e foi pesquisado com base na documentação e nos metadados atuais do **Requests 2.34.2**. Requests 2.34.2 exige Python 3.10 ou superior; este repositório valida os exemplos em Python 3.13.
+
+**Tempo estimado de estudo:** 270–360 minutos.
+
+## Objetivos de aprendizagem
+
+Ao final deste capítulo, você deverá conseguir:
+
+- explicar o modelo HTTP de requisição/resposta sem tratar uma chamada de API como uma função mágica;
+- executar GET, POST, PUT, PATCH e DELETE de forma intencional;
+- enviar parâmetros de query, headers, dados de formulário, corpos JSON, arquivos e dados de autenticação;
+- distinguir sucesso de transporte, sucesso HTTP e validade do payload;
+- configurar timeouts de conexão/leitura e entender o que eles não garantem;
+- tratar exceções do Requests sem esconder contexto útil;
+- usar `Session` para reutilização de conexões, cookies e padrões compartilhados;
+- entender redirects, verificação TLS, bundles de CA, proxies e configurações de ambiente;
+- processar respostas grandes por streaming e fechar recursos deterministicamente;
+- adicionar retries apenas quando a operação puder ser repetida com segurança;
+- proteger credenciais e evitar registrar segredos;
+- validar contratos de resposta em vez de confiar em JSON arbitrário;
+- construir testes HTTP determinísticos sem depender de um serviço público na internet.
+
+## 1. Por que `requests` existe
+
+A biblioteca padrão do Python consegue falar HTTP, mas `requests` oferece uma interface de nível mais alto para trabalho comum de cliente: URLs, parâmetros de query, headers, cookies, autenticação, corpos de requisição, verificação TLS, Sessions, streaming e exceções.
+
+A conveniência é valiosa, mas a rede continua sendo uma fronteira de sistema distribuído. Uma API legível não elimina latência, falhas parciais, regras de autenticação, erros de servidor, retries ou decisões de segurança.
+
+## 2. Pense em requisições e respostas
+
+Um cliente envia uma requisição HTTP contendo alguma combinação de:
+
+```text
+method + URL + headers + optional body
+```
+
+O servidor retorna uma resposta HTTP contendo:
+
+```text
+status code + headers + body
+```
+
+Seu código Python precisa raciocinar sobre as duas metades.
+
+## 3. Bibliotecas externas precisam de um contrato de versão
+
+Este repositório declara as dependências da Fase 9 em `requirements-external.txt`.
+
+Para este capítulo, o contrato é:
+
+```text
+requests >= 2.34 and < 2.35
+```
+
+A série 2.34 também introduziu tipagem inline no próprio Requests, permitindo que type checkers atuais consumam os tipos da API pública sem depender de um pacote separado de stubs.
+
+## 4. Instale o conjunto de dependências do repositório
+
+Crie e ative um ambiente virtual e então instale:
+
+```bash
+python -m pip install -r requirements-external.txt
+```
+
+Para experimentação isolada, `python -m pip install requests` é válido, mas um projeto deve registrar o intervalo de dependências suportado.
+
+## 5. Importe o pacote
+
+O import convencional é:
+
+```python
+import requests
+```
+
+O módulo de nível superior expõe funções convenientes como `get()`, `post()` e `delete()`, além de classes como `Session` e tipos de exceção em `requests.exceptions`.
+
+## 6. Uma URL faz parte do contrato de entrada
+
+Uma URL HTTP normalmente contém:
+
+```text
+scheme://host:port/path?query#fragment
+```
+
+Para um cliente HTTP, scheme, host, port, path e parâmetros de query afetam a requisição real. Fragments normalmente são interpretados no lado cliente e não são enviados como alvo da requisição HTTP.
+
+Não concatene fragmentos de URL não confiáveis de forma descuidada.
+
+## 7. Comece com uma requisição GET
+
+Um GET básico se parece com isto:
+
+```python
+import requests
+
+
+response = requests.get("https://example.com/api/items", timeout=(3, 10))
+print(response.status_code)
+```
+
+Este snippet é ilustrativo porque depende de um endpoint externo. Os exemplos executáveis publicados mais adiante usam um servidor local de teste.
+
+## 8. Status codes comunicam resultados HTTP
+
+Classes comuns são:
+
+```text
+1xx -> informational
+2xx -> successful response
+3xx -> redirection
+4xx -> client-side request problem
+5xx -> server-side failure
+```
+
+`200` não é o único sucesso. Um POST pode corretamente retornar `201 Created`, e um DELETE pode retornar `204 No Content`.
+
+## 9. Use `raise_for_status()` quando códigos HTTP sem sucesso forem falhas
+
+```python
+response = requests.get(url, timeout=(3, 10))
+response.raise_for_status()
+```
+
+Para status HTTP sem sucesso, `raise_for_status()` levanta `requests.HTTPError` e mantém a resposta ligada à exceção.
+
+Não descarte esse contexto ao relatar uma falha.
+
+## 10. Sucesso de transporte não é sucesso da aplicação
+
+Uma troca HTTP pode funcionar na camada de rede enquanto o servidor retorna `404`, `429` ou `500`.
+
+Da mesma forma, uma resposta `200` pode conter dados que violam o seu contrato de negócio.
+
+Um cliente robusto verifica mais do que “`requests.get()` retornou?”.
+
+## 11. `response.text` é texto decodificado
+
+```python
+text = response.text
+```
+
+Requests decodifica os bytes da resposta de acordo com informações de encoding e suas regras de detecção.
+
+Use texto quando o corpo for realmente textual e você quiser uma `str`.
+
+## 12. `response.content` fornece bytes brutos
+
+```python
+payload = response.content
+```
+
+Use bytes para arquivos binários, checksums, imagens, artefatos comprimidos ou qualquer formato em que converter para texto seria incorreto.
+
+## 13. Decodifique JSON com `response.json()`
+
+```python
+response = requests.get(url, timeout=(3, 10))
+response.raise_for_status()
+data = response.json()
+```
+
+`response.json()` faz o parse do corpo. Ele não prova que o status HTTP foi bem-sucedido.
+
+## 14. JSON inválido tem um modo de falha próprio
+
+Requests expõe `requests.exceptions.JSONDecodeError` para falhas de decodificação JSON.
+
+```python
+try:
+ data = response.json()
+except requests.exceptions.JSONDecodeError as exc:
+ raise RuntimeError("API returned invalid JSON") from exc
+```
+
+Uma resposta `204 No Content`, uma página HTML de erro ou JSON malformado podem fazer a decodificação falhar.
+
+## 15. O formato do JSON ainda precisa ser validado
+
+Mesmo JSON válido pode estar errado para o programa:
+
+```python
+if not isinstance(data, dict) or "items" not in data:
+ raise ValueError("Unexpected API response shape")
+```
+
+O parse responde “isto é JSON?”. A validação de contrato responde “este é o JSON esperado pela aplicação?”.
+
+## 16. Envie parâmetros de query com `params`
+
+Não monte query strings manualmente quando Requests pode codificá-las:
+
+```python
+response = requests.get(
+ url,
+ params={"status": "open", "limit": 20},
+ timeout=(3, 10),
+)
+```
+
+Requests cuida do encoding da query e expõe a URL final em `response.url`.
+
+## 17. Parâmetros repetidos podem usar sequências
+
+Algumas APIs esperam chaves repetidas. Requests aceita valores em sequência ou uma lista de tuplas de dois itens.
+
+```python
+params = [("tag", "python"), ("tag", "http")]
+response = requests.get(url, params=params, timeout=(3, 10))
+```
+
+Siga o contrato documentado da API-alvo.
+
+## 18. Headers da requisição são metadados
+
+```python
+headers = {
+ "Accept": "application/json",
+ "User-Agent": "study-client/1.0",
+}
+response = requests.get(url, headers=headers, timeout=(3, 10))
+```
+
+Headers podem comunicar preferências de representação, autenticação, requisições condicionais, tracing e outros metadados de protocolo.
+
+## 19. Não confunda headers de requisição e resposta
+
+Headers de requisição são enviados pelo cliente. Headers de resposta são devolvidos pelo servidor.
+
+```python
+content_type = response.headers.get("Content-Type")
+```
+
+Os headers de resposta do Requests se comportam como um mapping case-insensitive.
+
+## 20. Use um `User-Agent` descritivo quando fizer sentido
+
+Muitas APIs valorizam um identificador de cliente para operação e diagnóstico de suporte.
+
+Evite fingir ser um navegador não relacionado, a menos que o contrato da integração exija isso explicitamente.
+
+## 21. Envie dados de formulário com `data=`
+
+Para dados no estilo de formulário:
+
+```python
+response = requests.post(
+ url,
+ data={"username": "demo", "mode": "compact"},
+ timeout=(3, 10),
+)
+```
+
+Requests codifica um mapping passado por `data=` como dados de formulário.
+
+## 22. Envie JSON com `json=`
+
+Para APIs JSON, prefira o parâmetro dedicado:
+
+```python
+payload = {"name": "Nova", "active": True}
+response = requests.post(url, json=payload, timeout=(3, 10))
+response.raise_for_status()
+```
+
+`json=` serializa o valor e define um content type JSON apropriado.
+
+## 23. Não serialize JSON manualmente sem motivo
+
+Isto costuma ser menos claro:
+
+```python
+import json
+
+body = json.dumps(payload)
+response = requests.post(
+ url,
+ data=body,
+ headers={"Content-Type": "application/json"},
+ timeout=(3, 10),
+)
+```
+
+Use `json=` a menos que precise controlar precisamente os bytes serializados.
+
+## 24. PUT e PATCH expressam contratos de atualização diferentes
+
+A especificação HTTP e a documentação de cada API determinam a semântica. Em geral:
+
+```text
+PUT -> replace or set a representation at a target
+PATCH -> partially modify a representation
+```
+
+Não deduza o comportamento do servidor apenas pelo nome do método. Leia o contrato da API.
+
+## 25. DELETE pode ter sucesso sem corpo
+
+```python
+response = requests.delete(url, timeout=(3, 10))
+response.raise_for_status()
+```
+
+Um `204 No Content` bem-sucedido não deve ser seguido por `response.json()` incondicional.
+
+## 26. Idempotência importa antes de retries
+
+Uma operação é idempotente quando repetir a mesma requisição pretendida produz o mesmo efeito pretendido que executá-la uma vez.
+
+GET, HEAD, PUT e DELETE têm semântica idempotente no nível do método HTTP; POST em geral não tem. O comportamento da aplicação e chaves de idempotência podem adicionar garantias extras.
+
+## 27. Quase toda requisição de produção precisa de timeout
+
+Requests **não** aplica timeout por padrão.
+
+```python
+response = requests.get(url, timeout=10)
+```
+
+Sem um timeout explícito, um programa pode esperar indefinidamente por atividade de rede.
+
+## 28. Separe timeouts de conexão e leitura quando útil
+
+Requests aceita uma tupla:
+
+```python
+response = requests.get(url, timeout=(3, 15))
+```
+
+O primeiro valor é o connect timeout. O segundo é o read timeout.
+
+Isso costuma ser mais claro em integrações em que estabelecer conexão e aguardar bytes de resposta têm expectativas diferentes.
+
+## 29. Timeout do Requests não é um deadline total de relógio
+
+O comportamento documentado de timeout se baseia em inatividade do socket, não em uma duração máxima garantida para o download completo.
+
+Uma resposta lenta que continua entregando bytes pode durar mais que o valor nominal do read timeout.
+
+Se o fluxo precisa de um deadline geral rígido, projete-o separadamente.
+
+## 30. Requests possui uma hierarquia útil de exceções
+
+Exceções específicas do Requests herdam de `requests.exceptions.RequestException`.
+
+Subclasses importantes incluem:
+
+```text
+HTTPError
+ConnectionError
+Timeout
+TooManyRedirects
+JSONDecodeError
+SSLError
+```
+
+Capture de forma específica quando houver recuperação diferente.
+
+## 31. `Timeout` merece tratamento explícito
+
+```python
+try:
+ response = requests.get(url, timeout=(3, 10))
+except requests.Timeout as exc:
+ raise RuntimeError("Remote service timed out") from exc
+```
+
+Um timeout é diferente de um HTTP `500`: o cliente pode não saber se o servidor processou a requisição.
+
+Essa incerteza é crucial antes de repetir uma operação de escrita.
+
+## 32. `ConnectionError` cobre falhas de conexão de rede
+
+Falhas de DNS, conexão recusada e problemas de transporte relacionados podem surgir como `requests.ConnectionError`.
+
+Não converta isso em um resultado vazio e bem-sucedido. Preserve o sinal de falha ou aplique uma política de recuperação explícita.
+
+## 33. `HTTPError` fornece acesso à resposta
+
+```python
+try:
+ response.raise_for_status()
+except requests.HTTPError as exc:
+ status = exc.response.status_code
+ raise RuntimeError(f"API returned HTTP {status}") from exc
+```
+
+Evite incluir corpos arbitrários de resposta nos logs porque podem conter segredos ou dados pessoais.
+
+## 34. Um limite de `RequestException` pode adicionar contexto
+
+Na fronteira da aplicação, um wrapper pode acrescentar o nome da operação do serviço:
+
+```python
+try:
+ response = requests.get(url, timeout=(3, 10))
+ response.raise_for_status()
+except requests.RequestException as exc:
+ raise RuntimeError("Could not load catalog") from exc
+```
+
+Não capture `Exception` apenas para fazer uma falha de rede desaparecer.
+
+## 35. Redirects têm histórico
+
+Requests segue redirects em requisições GET comuns e expõe respostas anteriores por:
+
+```python
+for previous in response.history:
+ print(previous.status_code, previous.url)
+```
+
+A URL final fica disponível em `response.url`.
+
+## 36. Limite ou desative redirects quando o contrato exigir
+
+```python
+response = requests.get(
+ url,
+ allow_redirects=False,
+ timeout=(3, 10),
+)
+```
+
+Redirects podem importar para autenticação, auditoria, defesas contra SSRF e URLs assinadas.
+
+## 37. Autenticação Basic possui helper dedicado
+
+```python
+from requests.auth import HTTPBasicAuth
+
+
+response = requests.get(
+ url,
+ auth=HTTPBasicAuth("demo-user", "demo-password"),
+ timeout=(3, 10),
+)
+```
+
+Nunca grave credenciais reais em código-fonte, exemplos, commits ou logs.
+
+## 38. Bearer tokens normalmente são headers
+
+```python
+headers = {"Authorization": f"Bearer {token}"}
+response = requests.get(url, headers=headers, timeout=(3, 10))
+```
+
+O token deve vir de uma fonte segura de runtime, como secret manager ou variável de ambiente protegida, não de um arquivo do repositório.
+
+## 39. Redija segredos na observabilidade
+
+Não registre valores completos de:
+
+```text
+Authorization
+Proxy-Authorization
+Cookie
+Set-Cookie
+API keys
+signed URLs
+client certificates or private keys
+```
+
+Logs úteis podem conter método HTTP, host/path sanitizado, status code, tempo decorrido, contador de retry e identificadores de correlação.
+
+## 40. `Session` mantém estado entre requisições
+
+```python
+import requests
+
+
+with requests.Session() as session:
+ session.headers.update({"Accept": "application/json"})
+ response = session.get(url, timeout=(3, 10))
+```
+
+Uma Session pode persistir padrões e cookies entre chamadas.
+
+## 41. Sessions também reutilizam conexões
+
+Requests Sessions usam connection pooling do urllib3. Várias chamadas ao mesmo host podem reutilizar conexões subjacentes em vez de abrir uma nova conexão TCP/TLS a cada vez.
+
+Isso pode reduzir significativamente o overhead em fluxos repetidos de API.
+
+## 42. Padrões da Session podem ser sobrescritos por requisição
+
+Headers, autenticação, cookies, proxies e outros parâmetros em nível de Session são defaults convenientes, não globais imutáveis.
+
+Mantenha a configuração compartilhada intencional para não herdar credenciais de outro serviço acidentalmente.
+
+## 43. Feche Sessions deterministicamente
+
+Use context manager ou `close()`:
+
+```python
+with requests.Session() as session:
+ response = session.get(url, timeout=(3, 10))
+ response.raise_for_status()
+```
+
+A propriedade dos recursos deve ser visível em processos de longa duração.
+
+## 44. Cookies podem persistir em uma Session
+
+Um servidor pode definir cookies em uma resposta e esperá-los em requisições seguintes. Uma Session mantém um cookie jar entre chamadas.
+
+Para clientes de API, autenticação explícita por token costuma ser mais fácil de raciocinar, mas fluxos baseados em cookies ainda existem.
+
+## 45. Prepared requests expõem a requisição de saída exata
+
+Requests consegue montar um `PreparedRequest` antes de enviá-lo:
+
+```python
+from requests import Request, Session
+
+
+with Session() as session:
+ request = Request("GET", url, headers={"X-Trace": "demo"})
+ prepared = session.prepare_request(request)
+ print(prepared.method, prepared.url)
+```
+
+Isso é útil para assinatura avançada, inspeção ou mutação controlada da requisição.
+
+## 46. Prefira `Session.prepare_request()` quando o estado da Session importa
+
+Chamar `Request.prepare()` diretamente não aplica automaticamente todo o estado da Session.
+
+Se cookies, headers padrão ou autenticação da Session fazem parte do contrato, prepare a requisição por essa Session.
+
+## 47. Fluxos com prepared request precisam considerar o ambiente
+
+A documentação avançada do Requests observa que enviar manualmente uma prepared request pode ignorar configurações derivadas do ambiente se elas não forem mescladas explicitamente.
+
+Isso importa para configurações como bundles de CA e proxies.
+
+Preparação avançada de requisições deve ser intencional, não o padrão para chamadas comuns.
+
+## 48. Verificação de certificado HTTPS é habilitada por padrão
+
+Requests verifica certificados TLS do servidor em conexões HTTPS.
+
+```python
+response = requests.get("https://example.com", timeout=(3, 10))
+```
+
+Se a verificação falhar, Requests levanta `SSLError` em vez de confiar silenciosamente no peer.
+
+## 49. `verify=False` desabilita uma garantia importante de segurança
+
+```python
+response = requests.get(url, verify=False, timeout=(3, 10))
+```
+
+Isso aceita certificados possivelmente expirados, autoassinados ou para hostname incorreto e pode permitir ataques man-in-the-middle.
+
+Não resolva um problema de certificado em produção desabilitando globalmente a verificação.
+
+## 50. PKI privada deve usar um bundle de CA explícito
+
+Requests permite que `verify` aponte para um bundle de CA confiável:
+
+```python
+response = requests.get(
+ url,
+ verify="/path/to/company-ca-bundle.pem",
+ timeout=(3, 10),
+)
+```
+
+Requests também reconhece `REQUESTS_CA_BUNDLE`, com `CURL_CA_BUNDLE` como fallback em seu comportamento documentado de ambiente.
+
+## 51. Certificados de cliente suportam fluxos de mTLS
+
+O parâmetro `cert` pode apontar para um certificado de cliente ou para um par certificado/chave:
+
+```python
+response = requests.get(
+ url,
+ cert=("client.crt", "client.key"),
+ timeout=(3, 10),
+)
+```
+
+Arquivos de chave privada são segredos. Proteja-os como credenciais.
+
+## 52. Proxies podem vir de argumentos ou do ambiente
+
+Requests suporta configuração de proxy por requisição e settings derivados do ambiente.
+
+```python
+proxies = {"https": "http://proxy.example:8080"}
+response = requests.get(url, proxies=proxies, timeout=(3, 10))
+```
+
+Não coloque credenciais reais de proxy no código-fonte.
+
+## 53. `Session.trust_env` controla a integração com o ambiente
+
+Sessions confiam por padrão em configurações relevantes do ambiente, como proxies e fontes de autenticação.
+
+Se um cliente precisa operar independentemente da configuração ambiente do processo, avalie `session.trust_env` explicitamente e documente as consequências.
+
+## 54. Streaming evita carregar o corpo inteiro imediatamente
+
+```python
+with requests.get(url, stream=True, timeout=(3, 30)) as response:
+ response.raise_for_status()
+ for chunk in response.iter_content(chunk_size=64 * 1024):
+ if chunk:
+ process(chunk)
+```
+
+Streaming é útil para downloads grandes e processamento incremental.
+
+## 55. Respostas em streaming devem ser consumidas ou fechadas
+
+A reutilização de conexão depende da liberação da conexão subjacente.
+
+Usar a resposta como context manager torna a fronteira de propriedade explícita mesmo se o processamento gerar exceção.
+
+## 56. `iter_content()` geralmente é melhor que ler `raw` diretamente
+
+`iter_content()` coopera com o comportamento de decodificação do Requests e com iteração por chunks.
+
+Escolha o tamanho do chunk conforme o caso de uso e não assuma que cada chunk corresponde a uma mensagem lógica do servidor.
+
+## 57. Streaming por linhas é útil para protocolos orientados a linha
+
+`response.iter_lines()` pode processar uma resposta de streaming linha por linha.
+
+Tenha cuidado com linhas vazias de keep-alive, registros parciais e semântica de reconexão definida pela API específica.
+
+## 58. Grave downloads grandes com segurança
+
+Para um artefato importante, um padrão mais seguro é:
+
+```text
+download to temporary path
+-> verify status / size / checksum if available
+-> flush and close
+-> atomically move into final location
+```
+
+Isso evita que um download parcial pareça um arquivo concluído.
+
+## 59. Uploads multipart usam `files=`
+
+```python
+with open("report.txt", "rb") as file_handle:
+ response = requests.post(
+ url,
+ files={"file": ("report.txt", file_handle, "text/plain")},
+ timeout=(3, 30),
+ )
+```
+
+O arquivo deve ser aberto em modo binário para tratamento previsível de bytes.
+
+## 60. Requests não repete conexões com falha por padrão
+
+O `HTTPAdapter` embutido usa zero retries de conexão por padrão.
+
+Retries são uma política da aplicação, não algo que você deve presumir que ocorreu automaticamente.
+
+## 61. `HTTPAdapter` pode adicionar uma política de retry
+
+Para comportamento granular, a documentação do Requests usa a classe `Retry` do urllib3:
+
+```python
+from requests import Session
+from requests.adapters import HTTPAdapter
+from urllib3.util import Retry
+
+
+retry_policy = Retry(
+ total=3,
+ backoff_factor=0.5,
+ status_forcelist=[429, 502, 503, 504],
+ allowed_methods={"GET", "HEAD"},
+)
+
+with Session() as session:
+ session.mount("https://", HTTPAdapter(max_retries=retry_policy))
+ response = session.get(url, timeout=(3, 10))
+```
+
+A política deve ser projetada junto com o contrato da API, não copiada cegamente.
+
+## 62. Nunca repita uma escrita apenas porque ela falhou
+
+Um timeout após enviar um POST pode significar:
+
+```text
+client does not know whether server committed the operation
+```
+
+Repeti-lo cegamente pode criar duplicidades.
+
+Use chaves de idempotência, identificadores de operação, métodos seguros ou lógica de reconciliação quando o serviço oferecer essas garantias.
+
+## 63. Respeite `Retry-After` e contratos de rate limit
+
+Uma resposta `429 Too Many Requests` normalmente indica que o cliente deve desacelerar.
+
+Os headers exatos e o comportamento de retry são específicos da API. Leia o contrato do serviço e evite loops apertados que ampliem uma indisponibilidade.
+
+## 64. Backoff reduz tempestades de retry
+
+Retries normalmente devem aguardar entre tentativas. Backoff exponencial e jitter ajudam muitos clientes a não sincronizarem tentativas contra o mesmo serviço em recuperação.
+
+A política exata pertence ao desenho de confiabilidade do sistema.
+
+## 65. Response hooks podem adicionar comportamento transversal
+
+Requests suporta hooks de `response`:
+
+```python
+def record_status(response: requests.Response, *args: object, **kwargs: object) -> None:
+ print(response.status_code)
+
+
+response = requests.get(
+ url,
+ hooks={"response": record_status},
+ timeout=(3, 10),
+)
+```
+
+Hooks devem permanecer pequenos e tratar suas próprias premissas e falhas.
+
+## 66. Paginação é contrato da API, não recurso do Requests
+
+APIs podem paginar com:
+
+```text
+page/limit query parameters
+cursor tokens
+Link headers
+next URLs in JSON
+```
+
+O cliente deve parar na condição terminal documentada pelo serviço e se proteger contra loops infinitos acidentais.
+
+## 67. Link headers já são parseados
+
+Quando a resposta contém headers padrão de Web Linking, Requests expõe links parseados por:
+
+```python
+next_link = response.links.get("next")
+```
+
+Não presuma que toda API usa Link headers para paginação.
+
+## 68. Valide o media type da resposta quando importar
+
+Se o endpoint promete JSON, inspecione o contrato conforme necessário:
+
+```python
+content_type = response.headers.get("Content-Type", "")
+if "application/json" not in content_type.lower():
+ raise ValueError("Expected a JSON response")
+```
+
+Seja preciso o suficiente para a API integrada; media types podem conter parâmetros ou formatos vendor-specific `+json`.
+
+## 69. Logs HTTP exigem redação de dados sensíveis
+
+Um registro estruturado útil pode conter:
+
+```text
+service name
+operation
+HTTP method
+sanitized route
+status code
+elapsed time
+retry attempt
+correlation ID
+```
+
+Evite registrar URLs completas quando parâmetros de query puderem conter segredos ou dados pessoais.
+
+## 70. Testes determinísticos não devem depender da internet pública
+
+Um endpoint público pode ficar lento, indisponível, limitado por rate limit, bloqueado geograficamente ou alterado sem relação com seu repositório.
+
+Os exemplos executáveis deste capítulo iniciam um `ThreadingHTTPServer` local em `127.0.0.1`, exercitam Requests contra ele e então encerram o servidor. Assim testamos HTTP real sem dependência externa de rede.
+
+## 71. Exemplo prático: GET com parâmetros de query
+
+[`examples/get_with_query.py`](examples/get_with_query.py) envia um GET HTTP local real, deixa Requests codificar parâmetros de query, verifica o status, decodifica JSON e imprime o contrato de query parseado.
+
+Saída esperada:
+
+```text
+status: 200
+path: /items
+query: {'status': ['open'], 'limit': ['2']}
+```
+
+## 72. Exemplo prático: POST JSON
+
+[`examples/post_json.py`](examples/post_json.py) verifica que `json=` envia um payload JSON com o content type esperado.
+
+Saída esperada:
+
+```text
+status: 201
+created: {'name': 'Nova', 'active': True}
+content-type: application/json
+```
+
+## 73. Exemplo prático: padrões de Session
+
+[`examples/session_defaults.py`](examples/session_defaults.py) usa uma Session para aplicar headers compartilhados e confirma que o servidor local os recebeu.
+
+Saída esperada:
+
+```text
+client: python-study-guide
+auth-scheme: Bearer
+```
+
+O token é fictício e existe apenas dentro do processo local do exemplo.
+
+## 74. Exemplo prático: erros HTTP visíveis
+
+[`examples/http_error_handling.py`](examples/http_error_handling.py) recebe `404` intencionalmente e demonstra `raise_for_status()` preservando a resposta em `HTTPError`.
+
+Saída esperada:
+
+```text
+caught: HTTPError
+status: 404
+```
+
+## 75. Exemplo prático: download por streaming
+
+[`examples/stream_download.py`](examples/stream_download.py) transmite bytes locais determinísticos com `iter_content()` e fecha a resposta por context manager.
+
+Saída esperada:
+
+```text
+bytes: 12
+content: chunked-data
+```
+
+## 76. Erros comuns
+
+Evite estes padrões:
+
+| Erro | Por que é arriscado | Melhor abordagem |
+|---|---|---|
+| omitir `timeout` | requisição pode esperar indefinidamente | definir expectativas de conexão/leitura |
+| chamar `json()` e presumir sucesso | respostas de erro podem conter JSON válido | verificar status HTTP e contrato do payload |
+| usar `verify=False` em produção | desabilita verificação de identidade TLS | corrigir cadeia de confiança ou fornecer CA bundle |
+| repetir toda exceção | pode duplicar escritas ou piorar indisponibilidade | repetir apenas falhas seguras e classificadas |
+| criar nova Session para toda chamada | perde reutilização de conexão | possuir Session durante a vida lógica do cliente |
+| registrar Authorization | vaza credenciais | redigir segredos |
+| confiar em qualquer formato JSON | drift de schema vira bug oculto | validar campos/tipos necessários |
+| testar contra API pública de demonstração | CI fica externamente frágil | usar servidor local ou test double controlado |
+
+## 77. Tabela de decisão
+
+| Necessidade | Prefira |
+|---|---|
+| uma requisição simples | `requests.get/post/...` no módulo |
+| chamadas repetidas para um serviço | `requests.Session()` |
+| corpo JSON | `json=` |
+| corpo de formulário | `data=` |
+| parâmetros de query | `params=` |
+| resposta grande | `stream=True` + `iter_content()` |
+| falha HTTP deve interromper fluxo | `raise_for_status()` |
+| retries customizados | `HTTPAdapter` + política explícita de `Retry` |
+| CA privada | `verify=` |
+| teste determinístico em CI | servidor HTTP local / test double controlado |
+
+## 78. Referência rápida
+
+```python
+import requests
+
+
+with requests.Session() as session:
+ response = session.get(
+ "https://example.com/api/items",
+ params={"limit": 20},
+ headers={"Accept": "application/json"},
+ timeout=(3, 10),
+ )
+ response.raise_for_status()
+ data = response.json()
+```
+
+Antes da produção, acrescente regras específicas de autenticação, validação de schema, observabilidade, paginação, retry e segurança do serviço.
+
+## 79. Checklist de projeto de cliente HTTP
+
+Antes de publicar uma integração, responda:
+
+1. Quais métodos HTTP e rotas são permitidos?
+2. De onde vêm as base URLs?
+3. Todas as URLs externas são confiáveis ou validadas?
+4. Quais são os connect e read timeouts?
+5. Quais status codes são esperados?
+6. Quais media types e campos de resposta são obrigatórios?
+7. Como credenciais são obtidas, rotacionadas e redigidas?
+8. A verificação TLS está habilitada e a PKI privada está configurada corretamente?
+9. O cliente reutiliza uma Session?
+10. Quais falhas são retryable?
+11. Escritas repetidas são idempotentes ou protegidas por idempotency keys?
+12. Como paginação e rate limits são tratados?
+13. Respostas em streaming sempre são fechadas?
+14. Qual telemetria é segura para registrar?
+15. Os testes executam sem dependência de rede pública?
+
+## 80. Exercício integrado
+
+Construa um **cliente fictício de API de estoque** contra um servidor HTTP local de teste.
+
+Requisitos:
+
+1. Crie uma classe reutilizável `InventoryClient` que possua uma `requests.Session`.
+2. Aceite uma base URL no construtor.
+3. Aplique `Accept: application/json` em nível de Session.
+4. Adicione um método que liste itens com query params `status` e `limit`.
+5. Adicione um método que crie um item com corpo JSON.
+6. Use timeouts explícitos de conexão/leitura.
+7. Chame `raise_for_status()` antes de decodificar um payload de sucesso.
+8. Valide que objetos de item contêm `id` inteiro e `name` string.
+9. Traduza exceções do Requests para uma exceção própria da aplicação, preservando a original com `raise ... from`.
+10. Nunca registre o valor de Authorization.
+11. Adicione servidor local que retorne casos 200, 201, 404, 429 e JSON malformado.
+12. Teste que uma resposta `204` não é decodificada como JSON.
+13. Adicione paginação com condição terminal documentada.
+14. Explique quais operações você repetiria e por quê.
+15. Acrescente um desafio com idempotency key para uma escrita.
+
+Desafios de extensão:
+
+- transmitir um export gerado para arquivo temporário e renomeá-lo atomicamente;
+- configurar retry seguro para GET com backoff;
+- adicionar logs de tempo de resposta com correlation ID;
+- usar prepared request para inspecionar headers exatos antes do envio;
+- definir um pequeno modelo tipado depois de validar o contrato JSON.
+
+## 81. Conexões com conceitos anteriores
+
+`requests` se conecta diretamente ao material anterior:
+
+- **dicionários:** headers, query params, cookies e objetos JSON;
+- **funções/classes:** clientes reutilizáveis e fronteiras explícitas;
+- **exceções:** falhas de transporte, HTTP e decodificação;
+- **JSON:** serialização e parse de payloads;
+- **`pathlib`:** destinos seguros de download e caminhos de CA/certificado;
+- **logging:** chamadas observáveis com redação de segredos;
+- **`datetime`:** timestamps, validators de cache e campos de data de APIs;
+- **`os`:** variáveis de ambiente para configuração em runtime;
+- **`pandas`:** transformar dados tabulares recebidos de uma API;
+- **`openpyxl`:** transformar dados de API em workbooks Excel controlados.
+
+## 82. Referências primárias
+
+- [Documentação do Requests](https://requests.readthedocs.io/en/latest/)
+- [Requests Quickstart](https://requests.readthedocs.io/en/latest/user/quickstart/)
+- [Requests Advanced Usage](https://requests.readthedocs.io/en/latest/user/advanced/)
+- [Requests Developer Interface](https://requests.readthedocs.io/en/latest/api/)
+- [Requests no PyPI](https://pypi.org/project/requests/)
+- [Histórico de releases do Requests](https://github.com/psf/requests/releases)
+
+Quando este capítulo foi preparado, Requests 2.34.2 era a versão estável mais recente. O currículo mira a série 2.34.x em vez de depender de uma versão futura sem limite.
+
+## 83. Próximo capítulo
+
+A Fase 9 agora conecta três fronteiras práticas:
+
+```text
+pandas -> transform tabular data
+openpyxl -> construct and maintain Excel workbooks
+requests -> exchange data with HTTP services and APIs
+```
+
+A próxima biblioteca planejada é **`pytest`**, quando o foco passa de usar bibliotecas externas para provar sistematicamente que o comportamento Python continua correto.
+
+Antes de avançar, construa pelo menos um cliente contra um servidor HTTP local. Código confiável de API começa quando o comportamento da rede se torna algo que você consegue reproduzir, inspecionar e testar.
diff --git a/external-libraries/03-requests/examples/get_with_query.py b/external-libraries/03-requests/examples/get_with_query.py
new file mode 100644
index 0000000..d44a12b
--- /dev/null
+++ b/external-libraries/03-requests/examples/get_with_query.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+import json
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from threading import Thread
+from urllib.parse import parse_qs, urlparse
+
+import requests
+
+
+class QueryHandler(BaseHTTPRequestHandler):
+ def do_GET(self) -> None:
+ parsed = urlparse(self.path)
+ payload = {"path": parsed.path, "query": parse_qs(parsed.query)}
+ body = json.dumps(payload).encode("utf-8")
+
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+
+ def log_message(self, format: str, *args: object) -> None:
+ return
+
+
+server = ThreadingHTTPServer(("127.0.0.1", 0), QueryHandler)
+thread = Thread(target=server.serve_forever, daemon=True)
+thread.start()
+
+try:
+ host, port = server.server_address
+ response = requests.get(
+ f"http://{host}:{port}/items",
+ params={"status": "open", "limit": 2},
+ timeout=(1, 2),
+ )
+ response.raise_for_status()
+ data = response.json()
+
+ print(f"status: {response.status_code}")
+ print(f"path: {data['path']}")
+ print(f"query: {data['query']}")
+finally:
+ server.shutdown()
+ server.server_close()
+ thread.join()
diff --git a/external-libraries/03-requests/examples/http_error_handling.py b/external-libraries/03-requests/examples/http_error_handling.py
new file mode 100644
index 0000000..2c9f648
--- /dev/null
+++ b/external-libraries/03-requests/examples/http_error_handling.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from threading import Thread
+
+import requests
+
+
+class NotFoundHandler(BaseHTTPRequestHandler):
+ def do_GET(self) -> None:
+ self.send_response(404)
+ self.send_header("Content-Length", "0")
+ self.end_headers()
+
+ def log_message(self, format: str, *args: object) -> None:
+ return
+
+
+server = ThreadingHTTPServer(("127.0.0.1", 0), NotFoundHandler)
+thread = Thread(target=server.serve_forever, daemon=True)
+thread.start()
+
+try:
+ host, port = server.server_address
+ try:
+ response = requests.get(f"http://{host}:{port}/missing", timeout=(1, 2))
+ response.raise_for_status()
+ except requests.HTTPError as exc:
+ print(f"caught: {exc.__class__.__name__}")
+ print(f"status: {exc.response.status_code}")
+finally:
+ server.shutdown()
+ server.server_close()
+ thread.join()
diff --git a/external-libraries/03-requests/examples/post_json.py b/external-libraries/03-requests/examples/post_json.py
new file mode 100644
index 0000000..569aeaf
--- /dev/null
+++ b/external-libraries/03-requests/examples/post_json.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+import json
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from threading import Thread
+
+import requests
+
+
+class JsonHandler(BaseHTTPRequestHandler):
+ def do_POST(self) -> None:
+ length = int(self.headers["Content-Length"])
+ incoming = json.loads(self.rfile.read(length))
+ payload = {"created": incoming, "content_type": self.headers["Content-Type"]}
+ body = json.dumps(payload).encode("utf-8")
+
+ self.send_response(201)
+ self.send_header("Content-Type", "application/json")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+
+ def log_message(self, format: str, *args: object) -> None:
+ return
+
+
+server = ThreadingHTTPServer(("127.0.0.1", 0), JsonHandler)
+thread = Thread(target=server.serve_forever, daemon=True)
+thread.start()
+
+try:
+ host, port = server.server_address
+ response = requests.post(
+ f"http://{host}:{port}/items",
+ json={"name": "Nova", "active": True},
+ timeout=(1, 2),
+ )
+ response.raise_for_status()
+ data = response.json()
+
+ print(f"status: {response.status_code}")
+ print(f"created: {data['created']}")
+ print(f"content-type: {data['content_type']}")
+finally:
+ server.shutdown()
+ server.server_close()
+ thread.join()
diff --git a/external-libraries/03-requests/examples/session_defaults.py b/external-libraries/03-requests/examples/session_defaults.py
new file mode 100644
index 0000000..63ce7ac
--- /dev/null
+++ b/external-libraries/03-requests/examples/session_defaults.py
@@ -0,0 +1,50 @@
+from __future__ import annotations
+
+import json
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from threading import Thread
+
+import requests
+
+
+class SessionHandler(BaseHTTPRequestHandler):
+ def do_GET(self) -> None:
+ payload = {
+ "client": self.headers.get("X-Client"),
+ "authorization": self.headers.get("Authorization"),
+ }
+ body = json.dumps(payload).encode("utf-8")
+
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+
+ def log_message(self, format: str, *args: object) -> None:
+ return
+
+
+server = ThreadingHTTPServer(("127.0.0.1", 0), SessionHandler)
+thread = Thread(target=server.serve_forever, daemon=True)
+thread.start()
+
+try:
+ host, port = server.server_address
+ with requests.Session() as session:
+ session.headers.update(
+ {
+ "X-Client": "python-study-guide",
+ "Authorization": "Bearer example-token",
+ }
+ )
+ response = session.get(f"http://{host}:{port}/profile", timeout=(1, 2))
+ response.raise_for_status()
+ data = response.json()
+
+ print(f"client: {data['client']}")
+ print(f"auth-scheme: {data['authorization'].split()[0]}")
+finally:
+ server.shutdown()
+ server.server_close()
+ thread.join()
diff --git a/external-libraries/03-requests/examples/stream_download.py b/external-libraries/03-requests/examples/stream_download.py
new file mode 100644
index 0000000..e9fc9d8
--- /dev/null
+++ b/external-libraries/03-requests/examples/stream_download.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+from threading import Thread
+
+import requests
+
+
+PAYLOAD = b"chunked-data"
+
+
+class StreamHandler(BaseHTTPRequestHandler):
+ def do_GET(self) -> None:
+ self.send_response(200)
+ self.send_header("Content-Type", "application/octet-stream")
+ self.send_header("Content-Length", str(len(PAYLOAD)))
+ self.end_headers()
+ self.wfile.write(PAYLOAD[:6])
+ self.wfile.flush()
+ self.wfile.write(PAYLOAD[6:])
+
+ def log_message(self, format: str, *args: object) -> None:
+ return
+
+
+server = ThreadingHTTPServer(("127.0.0.1", 0), StreamHandler)
+thread = Thread(target=server.serve_forever, daemon=True)
+thread.start()
+
+try:
+ host, port = server.server_address
+ with requests.get(
+ f"http://{host}:{port}/download",
+ stream=True,
+ timeout=(1, 2),
+ ) as response:
+ response.raise_for_status()
+ chunks = list(response.iter_content(chunk_size=4))
+ content = b"".join(chunks)
+
+ print(f"bytes: {len(content)}")
+ print(f"content: {content.decode('utf-8')}")
+finally:
+ server.shutdown()
+ server.server_close()
+ thread.join()
diff --git a/external-libraries/README.es.md b/external-libraries/README.es.md
index 737255d..32d2be5 100644
--- a/external-libraries/README.es.md
+++ b/external-libraries/README.es.md
@@ -20,17 +20,17 @@ Las bibliotecas externas agregan una nueva responsabilidad de ingeniería: **con
1. ✅ [`pandas`: Trabajando con Datos Tabulares](01-pandas/README.es.md)
2. ✅ [`openpyxl`: Automatizando Libros de Excel](02-openpyxl/README.es.md)
-3. ⏳ `requests`: clientes HTTP y consumo de APIs
+3. ✅ [`requests`: Consumiendo APIs HTTP](03-requests/README.es.md)
4. ⏳ `pytest`: pruebas automatizadas
## Contrato de dependencias
Los ejemplos ejecutables publicados en esta fase usan las dependencias declaradas en [`requirements-external.txt`](../requirements-external.txt). El CI del repositorio instala ese archivo antes de ejecutar los ejemplos aprobados de bibliotecas externas.
-Los contratos actuales apuntan a **pandas 3.0.x** y **openpyxl 3.1.x**. pandas 3.0 soporta Python 3.11+, mientras PyPI declara Python 3.8+ para openpyxl 3.1.5. Este repositorio valida los ejemplos en Python 3.13.
+Los contratos actuales apuntan a **pandas 3.0.x**, **openpyxl 3.1.x** y **Requests 2.34.x**. pandas 3.0 soporta Python 3.11+, PyPI declara Python 3.8+ para openpyxl 3.1.5 y Requests 2.34.2 requiere Python 3.10+. Este repositorio valida los ejemplos en Python 3.13.
## Por qué esta fase viene ahora
Las fases anteriores establecieron colecciones, funciones, errores, archivos, módulos, CSV/JSON, fechas, rutas, logging, iteración, aritmética decimal y contratos de filesystem. Las bibliotecas externas deben construir sobre esas habilidades, no sustituirlas.
-El próximo capítulo planificado es **`requests`**.
+El próximo capítulo planificado es **`pytest`**.
diff --git a/external-libraries/README.md b/external-libraries/README.md
index 90f4a2d..4121237 100644
--- a/external-libraries/README.md
+++ b/external-libraries/README.md
@@ -20,17 +20,17 @@ External libraries add a new engineering responsibility: **dependency contracts*
1. ✅ [`pandas`: Working with Tabular Data](01-pandas/README.md)
2. ✅ [`openpyxl`: Automating Excel Workbooks](02-openpyxl/README.md)
-3. ⏳ `requests`: HTTP clients and API consumption
+3. ✅ [`requests`: Consuming HTTP APIs](03-requests/README.md)
4. ⏳ `pytest`: automated testing
## Dependency contract
Published executable examples from this phase use the dependencies declared in [`requirements-external.txt`](../requirements-external.txt). Repository CI installs that file before executing approved external-library examples.
-The current contracts target **pandas 3.0.x** and **openpyxl 3.1.x**. pandas 3.0 supports Python 3.11+, while PyPI declares Python 3.8+ for openpyxl 3.1.5. This repository validates the examples on Python 3.13.
+The current contracts target **pandas 3.0.x**, **openpyxl 3.1.x**, and **Requests 2.34.x**. pandas 3.0 supports Python 3.11+, PyPI declares Python 3.8+ for openpyxl 3.1.5, and Requests 2.34.2 requires Python 3.10+. This repository validates the examples on Python 3.13.
## Why this phase comes now
The earlier phases established collections, functions, errors, files, modules, CSV/JSON, dates, paths, logging, iteration, decimal arithmetic, and filesystem contracts. External libraries should build on those skills rather than replace them.
-The next planned chapter is **`requests`**.
+The next planned chapter is **`pytest`**.
diff --git a/external-libraries/README.pt-BR.md b/external-libraries/README.pt-BR.md
index 64f0502..8bdad01 100644
--- a/external-libraries/README.pt-BR.md
+++ b/external-libraries/README.pt-BR.md
@@ -20,17 +20,17 @@ Bibliotecas externas acrescentam uma nova responsabilidade de engenharia: **cont
1. ✅ [`pandas`: Trabalhando com Dados Tabulares](01-pandas/README.pt-BR.md)
2. ✅ [`openpyxl`: Automatizando Workbooks do Excel](02-openpyxl/README.pt-BR.md)
-3. ⏳ `requests`: clientes HTTP e consumo de APIs
+3. ✅ [`requests`: Consumindo APIs HTTP](03-requests/README.pt-BR.md)
4. ⏳ `pytest`: testes automatizados
## Contrato de dependências
Os exemplos executáveis publicados nesta fase usam as dependências declaradas em [`requirements-external.txt`](../requirements-external.txt). O CI do repositório instala esse arquivo antes de executar os exemplos aprovados de bibliotecas externas.
-Os contratos atuais têm como alvo **pandas 3.0.x** e **openpyxl 3.1.x**. O pandas 3.0 suporta Python 3.11+, enquanto o PyPI declara Python 3.8+ para openpyxl 3.1.5. Este repositório valida os exemplos em Python 3.13.
+Os contratos atuais têm como alvo **pandas 3.0.x**, **openpyxl 3.1.x** e **Requests 2.34.x**. O pandas 3.0 suporta Python 3.11+, o PyPI declara Python 3.8+ para openpyxl 3.1.5 e Requests 2.34.2 exige Python 3.10+. Este repositório valida os exemplos em Python 3.13.
## Por que esta fase vem agora
As fases anteriores estabeleceram coleções, funções, erros, arquivos, módulos, CSV/JSON, datas, caminhos, logging, iteração, aritmética decimal e contratos de filesystem. Bibliotecas externas devem construir sobre essas habilidades, não substituí-las.
-O próximo capítulo planejado é **`requests`**.
+O próximo capítulo planejado é **`pytest`**.
diff --git a/requirements-external.txt b/requirements-external.txt
index f3b6511..931a56a 100644
--- a/requirements-external.txt
+++ b/requirements-external.txt
@@ -2,3 +2,4 @@
# Keep version ranges aligned with the documented curriculum contracts.
pandas>=3.0,<3.1
openpyxl>=3.1,<3.2
+requests>=2.34,<2.35
diff --git a/scripts/example_manifest.txt b/scripts/example_manifest.txt
index 2e536c3..61fae5d 100644
--- a/scripts/example_manifest.txt
+++ b/scripts/example_manifest.txt
@@ -168,3 +168,8 @@ external-libraries/02-openpyxl/examples/styled_report.py
external-libraries/02-openpyxl/examples/table_and_validation.py
external-libraries/02-openpyxl/examples/workbook_basics.py
external-libraries/02-openpyxl/examples/write_only_export.py
+external-libraries/03-requests/examples/get_with_query.py
+external-libraries/03-requests/examples/http_error_handling.py
+external-libraries/03-requests/examples/post_json.py
+external-libraries/03-requests/examples/session_defaults.py
+external-libraries/03-requests/examples/stream_download.py