// aula 05 · Python · Web & APIs
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 desenvolvimentoAPI é 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.
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.
| 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 |
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 (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.
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 # }
dict → {} objeto JSONlist → [] array JSONstr → "string"int / float → númeroTrue / False → true / falseNone → nullQuando você usa requests, não precisa do módulo json manualmente:
resposta.json() já converte pra dictrequests.post(url, json=dados) já serializaInstale a biblioteca: pip install requests
Vamos usar a API ViaCEP — gratuita, sem chave, retorna endereço a partir de um CEP.
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
Usamos a JSONPlaceholder — uma API de testes que aceita qualquer dado.
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': ...}
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']}")
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.
Um código secreto único por usuário. Enviado no header ou na URL. É o método mais simples e comum.
Token gerado no login. Enviado no header Authorization: Bearer .... Expira após um tempo.
"Login com Google/GitHub". O usuário autoriza o app sem revelar a senha. Padrão de grandes plataformas.
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.
OPENAI_API_KEY=sk-proj-abc123... WEATHER_KEY=xyz789...
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
Instale: pip install flask
Um CRUD (Create, Read, Update, Delete) básico usando uma lista em memória.
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)
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")
| Código | Significado | Quando acontece |
|---|---|---|
| 200 OK | Sucesso | GET funcionou, dados retornados |
| 201 Created | Criado | POST funcionou, recurso criado |
| 204 No Content | Sem conteúdo | DELETE funcionou, nada a retornar |
| 301 Redirect | Redirecionado | URL mudou, Requests segue automaticamente |
| 400 Bad Request | Pedido inválido | Dados faltando ou formato errado |
| 401 Unauthorized | Não autenticado | Token ausente ou expirado |
| 403 Forbidden | Sem permissão | Autenticado, mas sem acesso |
| 404 Not Found | Não encontrado | Recurso não existe |
| 429 Too Many | Rate limit | Muitas requisições, espere |
| 500 Server Error | Erro no servidor | Bug na API que você está chamando |
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.