// aula 05 · Python · Web & APIs

APIs com Python —
Como programas conversam entre si


O que é uma API?

"Uma API é como um garçom num restaurante: você não precisa ir até a cozinha buscar sua comida. Você faz o pedido, o garçom leva até a cozinha e traz o resultado de volta."

— Analogia clássica do mundo do desenvolvimento

API — Application Programming Interface

API é uma interface que permite que dois programas se comuniquem. Ela define as regras de como fazer pedidos (requisições) e o que esperar em resposta. Você não precisa saber como o outro sistema funciona por dentro — só como chamá-lo corretamente.

Exemplos do dia a dia que usam APIs: o app do clima buscando a temperatura, o login com Google, o mapa no Uber, o pagamento pelo Stripe.

💻
Seu código
Python
📤
Requisição
GET / POST…
🌐
API
Servidor
📥
Resposta
JSON / XML…
🖥️
Seu app
usa o dado

REST & Métodos HTTP
O "idioma" mais usado para APIs na web

O que é REST?

REST (Representational State Transfer) é um estilo de arquitetura para APIs web. Uma API REST usa os métodos do protocolo HTTP e retorna dados em formato JSON. Ela é stateless — cada requisição é independente, o servidor não guarda memória da anterior.

Quase toda API que você vai usar (GitHub, OpenAI, Spotify, ViaCEP…) é REST.

🔧 Os 5 métodos HTTP que você precisa conhecer

Método O que faz Analogia Exemplo
GET Busca dados Ler um livro Buscar perfil de usuário
POST Envia / cria dados Escrever uma carta Criar um post novo
PUT Substitui por completo Trocar de livro Atualizar perfil inteiro
PATCH Altera parcialmente Corrigir uma frase Mudar só o e-mail
DELETE Remove dados Jogar fora Deletar uma conta

Anatomia de uma URL de API

▸ url_anatomia.txt
https://api.exemplo.com/v1/usuarios/42/posts?limite=10&pagina=2

https://         → protocolo (sempre HTTPS em produção)
api.exemplo.com  → domínio do servidor da API
/v1/             → versão da API (boa prática)
usuarios/42      → recurso + identificador
/posts           → sub-recurso (posts desse usuário)
?limite=10       → query param: filtros e opções
&pagina=2        → mais um query param

JSON — a língua das APIs
O formato de dados mais usado em APIs REST

O que é JSON?

JSON (JavaScript Object Notation) é um formato de texto leve para troca de dados. Apesar do nome ter "JavaScript", é independente de linguagem e é o padrão universal das APIs REST. Em Python, parece muito com um dicionário.

JSON ↔ Python — a conversão é automática

▸ json_exemplo.py
import json

# JSON que vem de uma API (string de texto)
texto_json = '''
{
  "nome": "Ana",
  "idade": 25,
  "cursos": ["Python", "Data Science"],
  "ativo": true
}
'''

# json.loads → transforma JSON em dicionário Python
dados = json.loads(texto_json)
print(dados['nome'])        # → Ana
print(dados['cursos'][0])   # → Python

# json.dumps → transforma dicionário Python em JSON
novo = {'produto': 'café', 'preco': 12.50}
texto = json.dumps(novo, ensure_ascii=False, indent=2)
print(texto)
# {
#   "produto": "café",
#   "preco": 12.5
# }

Python → JSON

  • dict{} objeto JSON
  • list[] array JSON
  • str"string"
  • int / float → número
  • True / Falsetrue / false
  • Nonenull

Dica com Requests

Quando você usa requests, não precisa do módulo json manualmente:

  • resposta.json() já converte pra dict
  • requests.post(url, json=dados) já serializa
  • Muito mais simples!

Consumindo APIs com Requests
Fazer chamadas GET, POST, enviar dados e tratar respostas

Instale a biblioteca: pip install requests

GET Buscando dados de uma API pública

Vamos usar a API ViaCEP — gratuita, sem chave, retorna endereço a partir de um CEP.

▸ busca_cep.py
import requests

cep = "01310100"  # Av. Paulista, SP
url = f"https://viacep.com.br/ws/{cep}/json/"

resposta = requests.get(url)

if resposta.status_code == 200:
    endereco = resposta.json()
    print(f"Logradouro: {endereco['logradouro']}")
    print(f"Bairro:     {endereco['bairro']}")
    print(f"Cidade:     {endereco['localidade']} - {endereco['uf']}")
else:
    print(f"Erro {resposta.status_code}")

# Saída:
# Logradouro: Avenida Paulista
# Bairro:     Bela Vista
# Cidade:     São Paulo - SP

POST Enviando dados para uma API

Usamos a JSONPlaceholder — uma API de testes que aceita qualquer dado.

▸ criar_post.py
import requests

url = "https://jsonplaceholder.typicode.com/posts"

novo_post = {
    "title":  "Minha primeira API com Python",
    "body":   "Funcionou! Estou enviando dados para uma API.",
    "userId": 1
}

# json= serializa o dicionário e seta o Content-Type automaticamente
resposta = requests.post(url, json=novo_post)

print(resposta.status_code)     # → 201 (Created)
print(resposta.json())          # → {'id': 101, 'title': ...}

Query params, headers e timeout

▸ requisicao_completa.py
import requests

# Query params como dicionário (requests monta a URL)
params = {'q': 'python', 'per_page': 5}

# Headers customizados (ex: User-Agent)
headers = {
    'User-Agent':    'MeuApp/1.0',
    'Accept':        'application/json',
    'Authorization': 'Bearer SEU_TOKEN_AQUI'
}

resposta = requests.get(
    "https://api.github.com/search/repositories",
    params=params,
    headers=headers,
    timeout=10   # desiste após 10 segundos
)

repos = resposta.json()['items']
for repo in repos:
    print(f"⭐ {repo['stargazers_count']:>6}  {repo['full_name']}")

Autenticação
Como provar ao servidor quem você é

Por que precisamos nos autenticar?

APIs públicas e gratuitas (como ViaCEP) não precisam de autenticação. Mas a maioria das APIs reais exige identificação para saber quem está fazendo a chamada, controlar limites de uso e proteger dados privados.

API Key

Um código secreto único por usuário. Enviado no header ou na URL. É o método mais simples e comum.

Bearer Token

Token gerado no login. Enviado no header Authorization: Bearer .... Expira após um tempo.

OAuth 2.0

"Login com Google/GitHub". O usuário autoriza o app sem revelar a senha. Padrão de grandes plataformas.

Exemplos práticos de autenticação

▸ autenticacao.py
import requests

# ── 1. API Key no Header (ex: OpenWeather, NewsAPI) ──
headers = {'X-Api-Key': 'sua_chave_aqui'}
requests.get('https://api.exemplo.com/dados', headers=headers)

# ── 2. API Key na URL (menos seguro, evitar) ──
url = f'https://api.exemplo.com/dados?api_key=sua_chave'
requests.get(url)

# ── 3. Bearer Token (JWT — mais comum em APIs modernas) ──
headers = {'Authorization': 'Bearer eyJhbGci...'}
requests.get('https://api.exemplo.com/perfil', headers=headers)

# ── 4. Basic Auth (usuário + senha) ──
requests.get('https://api.exemplo.com/dados',
            auth=('meu_usuario', 'minha_senha'))
⚠️

Nunca coloque chaves de API direto no código! Use variáveis de ambiente ou um arquivo .env com a biblioteca python-dotenv. Chaves no código vão parar no GitHub e serão roubadas em minutos.

Boas práticas com .env

▸ .env (nunca commitar este arquivo!)
OPENAI_API_KEY=sk-proj-abc123...
WEATHER_KEY=xyz789...
▸ app.py
from dotenv import load_dotenv
import os, requests

load_dotenv()  # carrega o .env

api_key = os.getenv('OPENAI_API_KEY')
headers = {'Authorization': f'Bearer {api_key}'}

# pip install python-dotenv

Criando sua própria API com Flask
Do zero a uma API funcional em menos de 30 linhas

Instale: pip install flask

API completa de gerenciamento de tarefas

Um CRUD (Create, Read, Update, Delete) básico usando uma lista em memória.

▸ api_tarefas.py
from flask import Flask, jsonify, request

app = Flask(__name__)

# "banco de dados" em memória
tarefas = [
    {"id": 1, "titulo": "Estudar Python", "feita": False},
    {"id": 2, "titulo": "Fazer APIs",    "feita": False},
]

# GET /tarefas → lista todas
@app.route("/tarefas", methods=["GET"])
def listar():
    return jsonify(tarefas)

# GET /tarefas/1 → busca uma por id
@app.route("/tarefas/<int:id>", methods=["GET"])
def buscar(id):
    t = next((t for t in tarefas if t["id"] == id), None)
    if not t:
        return jsonify({"erro": "não encontrada"}), 404
    return jsonify(t)

# POST /tarefas → cria nova
@app.route("/tarefas", methods=["POST"])
def criar():
    dados = request.get_json()
    nova = {"id": len(tarefas) + 1, "titulo": dados["titulo"], "feita": False}
    tarefas.append(nova)
    return jsonify(nova), 201

# DELETE /tarefas/1 → remove
@app.route("/tarefas/<int:id>", methods=["DELETE"])
def deletar(id):
    global tarefas
    tarefas = [t for t in tarefas if t["id"] != id]
    return jsonify({"mensagem": "deletada"}), 200

if __name__ == "__main__":
    app.run(debug=True)

Testando a API — como chamar cada rota

▸ testar_api.py (rode em outro terminal)
import requests

BASE = "http://localhost:5000"

# Listar todas
print(requests.get(f"{BASE}/tarefas").json())

# Criar nova tarefa
nova = requests.post(f"{BASE}/tarefas", json={"titulo": "Dormir cedo"})
print(nova.status_code)   # → 201
print(nova.json())        # → {'id': 3, 'titulo': 'Dormir cedo', 'feita': False}

# Buscar por ID
print(requests.get(f"{BASE}/tarefas/1").json())

# Deletar
requests.delete(f"{BASE}/tarefas/2")

Status Codes & Tratamento de Erros
Entender o que deu certo ou errado em cada chamada

Códigos de status HTTP

CódigoSignificadoQuando acontece
200 OKSucessoGET funcionou, dados retornados
201 CreatedCriadoPOST funcionou, recurso criado
204 No ContentSem conteúdoDELETE funcionou, nada a retornar
301 RedirectRedirecionadoURL mudou, Requests segue automaticamente
400 Bad RequestPedido inválidoDados faltando ou formato errado
401 UnauthorizedNão autenticadoToken ausente ou expirado
403 ForbiddenSem permissãoAutenticado, mas sem acesso
404 Not FoundNão encontradoRecurso não existe
429 Too ManyRate limitMuitas requisições, espere
500 Server ErrorErro no servidorBug na API que você está chamando

Tratamento de erros robusto

▸ erros_robustos.py
import requests
from requests.exceptions import Timeout, ConnectionError, HTTPError

def buscar_dados(url):
    try:
        resposta = requests.get(url, timeout=8)

        # Lança HTTPError p/ códigos 4xx e 5xx
        resposta.raise_for_status()

        return resposta.json()

    except Timeout:
        print("⏱️  Servidor demorou demais")
    except ConnectionError:
        print("📡 Sem conexão com a internet")
    except HTTPError as e:
        codigo = e.response.status_code
        if codigo == 401:
            print("🔑 Token inválido ou expirado")
        elif codigo == 404:
            print("🔍 Recurso não encontrado")
        elif codigo == 429:
            print("🚦 Rate limit — aguarde antes de tentar novamente")
        else:
            print(f"❌ Erro HTTP {codigo}")
    return None

dados = buscar_dados("https://viacep.com.br/ws/01310100/json/")
if dados:
    print(dados['logradouro'])

Sempre use timeout=! Sem timeout, seu código pode ficar travado para sempre esperando um servidor que não vai responder. Um valor de 5 a 15 segundos é razoável para a maioria dos casos.