.claude/skills/documentation/postman/SKILL.md
Skill completa para desenvolvimento e documentação de APIs usando Postman, incluindo automação, CI/CD, e geração a partir de OpenAPI.
npx skillsauth add LucasBiason/engineering-knowledge-base postman-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Skill completa para desenvolvimento, documentação e automação de APIs usando Postman, seguindo padrões de produção e melhores práticas de mercado.
Aplicar esta skill quando:
{
"info": {
"_postman_id": "unique-id",
"name": "Nome da API - Documentação",
"description": "# Descrição Completa\n\n## Visão Geral\n...",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
}
}
Requisitos:
Collection/
├── Authentication/
│ ├── Login
│ ├── Refresh Token
│ └── Validate Token
├── [Módulo Principal]/
│ ├── [Submódulo]/
│ │ ├── List [Resource]
│ │ ├── Create [Resource]
│ │ ├── Get [Resource]
│ │ ├── Update [Resource]
│ │ └── Delete [Resource]
│ └── [Outro Submódulo]/
└── Dashboard/
├── Filters
├── Stats
└── Charts/
Padrões de Nomenclatura:
Authentication, Financial KPIs)List Contracts, Create Contract){
"id": "postman-env-default",
"name": "Local Env",
"values": [
{ "key": "base_url", "value": "http://localhost:8000", "enabled": true },
{ "key": "auth_token", "value": "", "enabled": true },
{ "key": "refresh_token", "value": "", "enabled": true },
{ "key": "auth_expires_at", "value": "", "enabled": true },
{ "key": "user_email", "value": "[email protected]", "enabled": true },
{ "key": "user_password", "value": "changeme", "enabled": true }
]
}
Variáveis padrão:
base_url: URL base da aplicaçãoauth_token: Token JWT (preenchido automaticamente)refresh_token: Refresh token (preenchido automaticamente)auth_expires_at: Timestamp de expiração do tokenuser_email: Email do usuário de testeuser_password: Senha do usuário de testePara Django DRF:
admin_username: Username do adminadmin_password: Password do adminencoded_token: Token codificado (alternativa)Snippet obrigatório para todas as collections com JWT:
// Collection-level pre-request: attach Bearer token and auto-refresh
// 1. Attach Authorization header if token exists
if (pm.environment.get('auth_token')) {
pm.request.headers.upsert({
key: 'Authorization',
value: 'Bearer ' + pm.environment.get('auth_token')
});
}
// 2. Auto-refresh token if expired
let expiresAt = pm.environment.get('auth_expires_at');
if (expiresAt && Date.now() >= parseInt(expiresAt)) {
console.log('Access token expired — attempting refresh...');
let refreshReq = {
url: pm.environment.get('base_url') + '/auth/refresh',
method: 'POST',
header: { 'Content-Type': 'application/json' },
body: {
mode: 'raw',
raw: JSON.stringify({
refresh_token: pm.environment.get('refresh_token')
})
}
};
pm.sendRequest(refreshReq, function (err, res) {
if (!err && res.code === 200) {
let json = res.json();
if (json.access_token) {
pm.environment.set('auth_token', json.access_token);
}
if (json.refresh_token) {
pm.environment.set('refresh_token', json.refresh_token);
}
if (json.expires_in) {
pm.environment.set('auth_expires_at', Date.now() + (json.expires_in * 1000));
}
} else {
console.error('Refresh failed', err || res);
}
});
}
Para endpoints que requerem autenticação mas não têm token:
// Verificar se token existe, se não, fazer login
if (!pm.environment.get("auth_token")) {
pm.sendRequest({
url: pm.environment.get("base_url") + "/auth/login",
method: "POST",
header: {
"Content-Type": "application/json"
},
body: {
mode: "raw",
raw: JSON.stringify({
email: pm.environment.get("user_email"),
password: pm.environment.get("user_password")
})
}
}, function (err, res) {
if (res.code === 200) {
const jsonData = res.json();
if (jsonData.access_token) {
pm.environment.set("auth_token", jsonData.access_token);
}
if (jsonData.refresh_token) {
pm.environment.set("refresh_token", jsonData.refresh_token);
}
if (jsonData.expires_in) {
pm.environment.set("auth_expires_at", Date.now() + (jsonData.expires_in * 1000));
}
}
});
}
// Gerar timestamp atual
pm.environment.set("current_timestamp", new Date().toISOString());
// Gerar data futura
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 30);
pm.environment.set("future_date", futureDate.toISOString().split('T')[0]);
Snippet obrigatório para endpoint /auth/login:
// Test: Save tokens from login response
pm.test("Login successful", function () {
pm.response.to.have.status(200);
});
let res = pm.response.json();
if (res.access_token) {
pm.environment.set('auth_token', res.access_token);
}
if (res.refresh_token) {
pm.environment.set('refresh_token', res.refresh_token);
}
if (res.expires_in) {
let expiresAt = Date.now() + (res.expires_in * 1000);
pm.environment.set('auth_expires_at', expiresAt);
}
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
pm.test("Response has required fields", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property("id");
pm.expect(jsonData).to.have.property("name");
});
pm.test("Response data types are correct", function () {
const jsonData = pm.response.json();
pm.expect(jsonData.id).to.be.a("string");
pm.expect(jsonData.count).to.be.a("number");
});
if (pm.response.code === 200) {
const jsonData = pm.response.json();
pm.environment.set("resource_id", jsonData.id);
pm.environment.set("logged_user", jsonData.user.id);
}
Cada endpoint DEVE ter descrição completa em Markdown:
## [Nome do Endpoint]
### Validações
- campo (tipo): Descrição → status_code: "mensagem de erro"
- campo (tipo): Descrição → status_code: "mensagem de erro"
### Regras de Negócio
- Regra 1
- Regra 2
- Regra 3
### Erros
- status_code: Descrição do erro
- status_code: Descrição do erro
Exemplo:
## Login
### Validações
- email (string): Obrigatório → 422: "Email is required"
- password (string): Obrigatório → 422: "Password is required"
### Regras de Negócio
- Credenciais devem estar corretas
- Usuário deve estar ativo
- Token válido por 24h (access_token)
- Refresh token válido por 7 dias
### Erros
- 422: Email/Password ausente
- 401: Credenciais inválidas
- 403: Usuário inativo
Cada endpoint DEVE ter:
Exemplo de Sucesso (status 200/201)
Exemplos de Erro de Validação (status 400/422)
Exemplos de Erro de Negócio (status 404/403/401)
{
"name": "✅ Sucesso - Contrato criado",
"code": 201,
"body": "{\n \"id\": 1,\n \"term_number\": \"123/2024\",\n \"status\": \"active\"\n}"
}
{
"name": "❌ 422 - term_number ausente",
"code": 422,
"body": "{\n \"detail\": [\n {\n \"type\": \"value_error.missing\",\n \"loc\": [\"body\", \"term_number\"],\n \"msg\": \"This field is required.\"\n }\n ]\n}"
}
{
"key": "year",
"value": "2024",
"description": "Ano para filtrar os dados (formato: YYYY).",
"disabled": true
}
Requisitos:
{
"variable": [
{
"key": "id",
"value": "1",
"description": "ID do contrato (número inteiro)."
}
]
}
Para JSON:
{{variable_name}}Para multipart/form-data:
### Campo: status
**Tipo:** ChoiceField
**Valores aceitos:**
- `PENDING`: Pendente
- `PROCESSING`: Processando
- `COMPLETED`: Concluído
- `FAILED`: Falha
Endpoints típicos:
POST /auth/login → retorna { access_token, refresh_token, expires_in }POST /auth/refresh → retorna novo access_token (rotacionar refresh token)POST /auth/logout → invalidar refresh token (DB ou blacklist)Configuração recomendada:
access_token: expiração curta (ex: 15 minutos)refresh_token: long-lived (ex: 7 dias)Para APIs que usam sessão:
Set-Cookie se necessário// Para Django DRF com Session
pm.request.headers.upsert({
key: 'X-CSRFToken',
value: pm.cookies.get('csrftoken')
});
Newman é o CLI (Command Line Interface) do Postman. Ele NÃO converte documentação em collection. Ele executa uma collection Postman já existente.
Diferença importante:
Arquivo: scripts/run_newman.sh
Este script executa uma collection Postman usando o Newman:
#!/usr/bin/env bash
# Runs Newman against the collection and environment.
# Usage: ./run_newman.sh [collection.json] [environment.json]
COLLECTION=${1:-postman/collection.json}
ENV=${2:-postman/environment.json}
REPORT_DIR=${REPORT_DIR:-reports/postman}
mkdir -p "$REPORT_DIR"
# EXECUTA a collection (não converte!)
npx newman run "$COLLECTION" -e "$ENV" \
--reporters cli,junit,html \
--reporter-junit-export "$REPORT_DIR/junit-results.xml" \
--reporter-html-export "$REPORT_DIR/report.html"
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "Newman tests failed (exit $EXIT_CODE)"
exit $EXIT_CODE
fi
echo "Newman finished."
# Executar com defaults (usa postman/collection.json)
./scripts/run_newman.sh
# Especificar collection e environment
./scripts/run_newman.sh postman/collection.json postman/environment.json
O que acontece:
collection.json)Arquivo: .github/workflows/postman-ci.yml
name: Postman Collection Tests
on:
push:
branches: [ main, master ]
pull_request:
jobs:
postman-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci || npm i
- name: Install jq (for environment manipulation)
run: sudo apt-get update && sudo apt-get install -y jq
- name: Run Newman (Postman tests)
env:
BASE_URL: ${{ secrets.BASE_URL }}
USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
run: |
# Write environment with secrets
jq '.values[] |= (if .key=="base_url" then .value="'$BASE_URL'" elif .key=="user_email" then .value="'$USER_EMAIL'" elif .key=="user_password" then .value="'$USER_PASSWORD'" else . end)' postman/environment.json > tmp_env.json
npx newman run postman/collection.json -e tmp_env.json \
--reporters cli,junit \
--reporter-junit-export postman/junit-results.xml
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: postman-results
path: postman/junit-results.xml
Configurar no GitHub Repository Settings → Secrets:
BASE_URL: URL base da API (ex: https://api.example.com)TEST_USER_EMAIL: Email do usuário de testeTEST_USER_PASSWORD: Senha do usuário de testeConversão (OpenAPI → Postman):
openapi-to-postmanv2 ou postman-collection-transformerExecução (Newman):
newman (via run_newman.sh)Express.js:
npm install swagger-jsdoc
# Gera openapi.json automaticamente
Django DRF:
pip install drf-spectacular
# Gera openapi.json via /api/schema/
FastAPI:
# OpenAPI gerado automaticamente em /openapi.json
Script: generate-from-openapi.sh
#!/usr/bin/env bash
# Gera collection Postman a partir de OpenAPI/Swagger
# Usage: ./generate-from-openapi.sh [openapi.json] [output-dir]
OPENAPI_FILE=${1:-openapi.json}
OUTPUT_DIR=${2:-postman}
# Instalar ferramenta se necessário
if ! command -v openapi-to-postmanv2 &> /dev/null; then
npm install -g openapi-to-postmanv2
fi
# Converter OpenAPI para Postman
openapi-to-postmanv2 -s "$OPENAPI_FILE" -o "$OUTPUT_DIR/collection.json"
echo "✅ Collection gerada em $OUTPUT_DIR/collection.json"
Uso:
# Converter openapi.json para postman/collection.json
./generate-from-openapi.sh openapi.json postman
# Ou especificar arquivo diferente
./generate-from-openapi.sh docs/swagger.json postman
Após a conversão, você precisa adicionar manualmente (ou via script):
Exemplo de script Python para injetar scripts:
import json
# Ler collection gerada
with open('postman/collection.json', 'r') as f:
collection = json.load(f)
# Adicionar pre-request script (collection-level)
collection['event'] = [{
"listen": "prerequest",
"script": {
"exec": [
"// Auto-attach Bearer token",
"if (pm.environment.get('auth_token')) {",
" pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + pm.environment.get('auth_token') });",
"}"
],
"type": "text/javascript"
}
}]
# Salvar collection atualizada
with open('postman/collection.json', 'w') as f:
json.dump(collection, f, indent=2)
Agora sim, usar o run_newman.sh:
# Executar testes da collection gerada
./run_newman.sh postman/collection.json postman/environment.json
postman/collections.publish1. OpenAPI/Swagger (gerado pelo framework)
↓
2. openapi-to-postmanv2 (CONVERTE)
↓
3. postman/collection.json (collection gerada)
↓
4. Adicionar scripts manualmente (autenticação, testes)
↓
5. run_newman.sh (EXECUTA testes)
↓
6. Relatórios (junit-results.xml, report.html)
Estrutura mínima:
{
"info": {
"name": "API - Boilerplate (Auth + CRUD)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"description": "Coleção padrão: Auth (JWT), exemplos CRUD."
},
"item": [
{
"name": "Auth",
"item": [
{
"name": "POST /auth/login",
"request": {
"method": "POST",
"header": [
{ "key": "Content-Type", "value": "application/json" }
],
"body": {
"mode": "raw",
"raw": "{\n \"email\": \"{{user_email}}\",\n \"password\": \"{{user_password}}\"\n}"
},
"url": {
"raw": "{{base_url}}/auth/login",
"host": ["{{base_url}}"],
"path": ["auth","login"]
}
},
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('status 200', function () { pm.response.to.have.status(200); });",
"let res = pm.response.json();",
"if(res.access_token){ pm.environment.set('auth_token', res.access_token); }",
"if(res.refresh_token){ pm.environment.set('refresh_token', res.refresh_token); }",
"if(res.expires_in){ let expiresAt = Date.now() + (res.expires_in * 1000); pm.environment.set('auth_expires_at', expiresAt); }"
],
"type": "text/javascript"
}
}
]
}
]
}
],
"event": [
{
"listen": "prerequest",
"script": {
"exec": [
"// Collection-level pre-request: attach Authorization header if auth_token present",
"if (pm.environment.get('auth_token')) {",
" pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + pm.environment.get('auth_token') });",
"}",
"",
"// Auto-refresh if token expired",
"let expiresAt = pm.environment.get('auth_expires_at');",
"if (expiresAt && Date.now() >= parseInt(expiresAt)) {",
" console.log('Access token expired — attempting refresh...');",
" let refreshReq = {",
" url: pm.environment.get('base_url') + '/auth/refresh',",
" method: 'POST',",
" header: { 'Content-Type': 'application/json' },",
" body: { mode: 'raw', raw: JSON.stringify({ refresh_token: pm.environment.get('refresh_token') }) }",
" };",
" pm.sendRequest(refreshReq, function (err, res) {",
" if (!err && res.code === 200) {",
" let json = res.json();",
" if (json.access_token) { pm.environment.set('auth_token', json.access_token); }",
" if (json.refresh_token) { pm.environment.set('refresh_token', json.refresh_token); }",
" if (json.expires_in) { pm.environment.set('auth_expires_at', Date.now() + (json.expires_in * 1000)); }",
" } else { console.error('Refresh failed', err || res); }",
" });",
"}"
],
"type": "text/javascript"
}
}
]
}
Endpoints típicos:
POST /auth/login → { access_token, refresh_token, expires_in }POST /auth/refresh → novo access_token (rotacionar refresh token)POST /auth/logout → invalidar refresh tokenPacotes recomendados:
jsonwebtoken para gerar tokensexpress-jwt ou middleware customizado para validaçãoConfiguração de desenvolvimento:
expires_in curto (ex: 15 minutos) para access_tokenConfiguração:
djangorestframework-simplejwt ou dj-rest-authsettings.py/api/token/ e /api/token/refresh/Para sessões (cookies):
Variáveis de ambiente:
admin_username: Username do adminadmin_password: Password do adminencoded_token: Token codificado (alternativa)OpenAPI automático:
openapi-to-postmanv2 para converterQuando encontrar erros durante a documentação:
Documentar o erro:
Informar necessidade de correção:
Reteste completo:
projeto/
├── postman/
│ ├── collection.json # Collection principal
│ ├── environment.json # Environment padrão
│ └── environment.prod.json # Environment de produção (gitignored)
├── scripts/
│ └── run_newman.sh # Script de execução Newman
├── .github/
│ └── workflows/
│ └── postman-ci.yml # CI/CD workflow
└── reports/
└── postman/ # Relatórios Newman (gitignored)
├── junit-results.xml
└── report.html
core/docs/programming/postman-documentation-standards.md// Pre-request snippet: attach Bearer and refresh if expired
if (pm.environment.get('auth_token')) {
pm.request.headers.upsert({
key: 'Authorization',
value: 'Bearer ' + pm.environment.get('auth_token')
});
}
let expiresAt = pm.environment.get('auth_expires_at');
if (expiresAt && Date.now() >= parseInt(expiresAt)) {
pm.sendRequest({
url: pm.environment.get('base_url') + '/auth/refresh',
method: 'POST',
header: { 'Content-Type': 'application/json' },
body: {
mode: 'raw',
raw: JSON.stringify({
refresh_token: pm.environment.get('refresh_token')
})
}
}, function (err, res) {
if (!err && res.code === 200) {
let j = res.json();
if (j.access_token) pm.environment.set('auth_token', j.access_token);
if (j.refresh_token) pm.environment.set('refresh_token', j.refresh_token);
if (j.expires_in) pm.environment.set('auth_expires_at', Date.now() + (j.expires_in * 1000));
}
});
}
pm.test("Login works", function () {
pm.response.to.have.status(200);
});
let r = pm.response.json();
if (r.access_token) pm.environment.set('auth_token', r.access_token);
if (r.refresh_token) pm.environment.set('refresh_token', r.refresh_token);
if (r.expires_in) pm.environment.set('auth_expires_at', Date.now() + (r.expires_in * 1000));
Fluxo recomendado:
swagger-jsdoc, Django: drf-spectacular, FastAPI: automático)openapi-to-postmanv2)postman/ no repocollections.publishTemplates e Exemplos: Ver core/templates/postman-collection/ para templates completos de collection, environment e scripts de automação
testing
Execução e análise de testes automatizados
development
Gera resumos didáticos extensos e estruturados de aulas/cursos para cards do Notion. Use ao resumir aulas, apostilas, transcrições ou materiais de estudo para incluir no corpo do card (não apenas no campo Descrição), com flashcards, exemplos de código, diagramas Mermaid, mapa conceitual e perguntas de reforço.
development
Padroniza documentação existente no formato canônico Spec-Driven. Remove duplicação e melhora rastreabilidade.
development
Processo universal e repetível para criar especificações a partir de qualquer input (texto, docs, código). Usado em Plan mode.