A NestJS API for automating Threads posts with OAuth2 authentication.
bun install
- Create a
.env
file in the root directory:
# App Configuration
THREADS_APP_ID=your_app_id
THREADS_APP_SECRET=your_app_secret
THREADS_REDIRECT_URI=https://threads-bot-api.loca.lt/threads/callback
- Configure Meta App Settings:
- Add domain:
https://threads-bot-api.loca.lt
- Add to Valid OAuth Redirect URIs:
https://threads-bot-api.loca.lt/threads/callback
- Set Match Type:
Match prefix
- Set Prefetch:
HTML
The Threads API requires HTTPS. We use Docker and localtunnel to handle this:
- Start the services:
docker-compose up -d
- Get the tunnel password:
# Check localtunnel logs for the password
docker-compose logs localtunnel | grep "Tunnel Password"
- Your API will be available at:
- HTTPS: https://threads-bot-api.loca.lt
- First-time visitors will need the tunnel password
- Password is your public IP (shown in localtunnel logs)
- Local: http://localhost:3000
-
Share the tunnel password with your testers/users
-
Check the logs:
# All services
docker-compose logs -f
# Just API
docker-compose logs -f api
# Just localtunnel
docker-compose logs -f localtunnel
- Stop the services:
docker-compose down
The API implements OAuth2 authentication for Threads with CSRF protection:
-
Get Authorization URL or Direct Redirect
# Get URL as JSON curl https://threads-bot-api.loca.lt/threads/auth # Direct redirect to Threads auth page curl -L https://threads-bot-api.loca.lt/threads/auth?redirect=true
-
User Authorization
- User authorizes your app on Threads
- Threads redirects back with auth code and state
-
Exchange Code for Token
# Handled automatically by callback endpoint GET /threads/callback?code=AUTHORIZATION_CODE&state=STATE_TOKEN
-
Using the Token
curl -X POST https://threads-bot-api.loca.lt/threads/post \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "text": "Hello from API!", "mediaUrls": ["https://example.com/image.jpg"] }'
# Run locally
bun run start:dev
# Run with Docker
docker-compose up
- Start auth flow (two options):
# Get auth URL
curl https://threads-bot-api.loca.lt/threads/auth
# Or redirect directly
curl -L https://threads-bot-api.loca.lt/threads/auth?redirect=true
-
Complete authorization on Threads
-
Automatic callback handling with:
- CSRF verification
- Error handling
- Token generation
-
Create a post:
curl -X POST https://threads-bot-api.loca.lt/threads/post \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "Hello from API!",
"mediaUrls": ["https://example.com/image.jpg"]
}'
- Swagger UI: http://localhost:3000/api
- Scalar Docs: http://localhost:3000/docs
The API requires the following Threads permissions:
threads_basic
- Basic profile accessthreads_content_publish
- Ability to create poststhreads_manage_insights
(optional) - Access to post insightsthreads_manage_replies
(optional) - Manage post repliesthreads_read_replies
(optional) - Read post replies
- CSRF protection with state parameter
- Environment variable validation
- Automatic state cleanup (30-minute expiry)
- Comprehensive error handling
- Request validation
- Secure token handling
The API handles:
- Invalid/expired state tokens
- Missing authorization codes
- Failed token exchanges
- Invalid tokens
- Missing permissions
- Rate limits
- Configuration errors
- Never commit
.env
file - Store tokens securely
- Use HTTPS in production
- Validate redirect URIs
- Implement rate limiting
- Monitor state token usage
The API includes built-in monitoring with Grafana and Prometheus:
- Access dashboards:
- Grafana: http://localhost:3001 (admin/admin)
- Prometheus: http://localhost:9090
- Available metrics:
- API health status
- Tunnel connectivity
- Request rates
- Error rates
- Token usage
- Rate limit status
- Alerts:
- Tunnel disconnection
- High error rates
- Rate limit warnings
- Token expiration
-
Container Registry Setup
# 1. Login to GitHub Container Registry echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin # 2. Configure Registry Visibility gh api \ --method PUT \ -H "Accept: application/vnd.github+json" \ /user/packages/container/threads-bot-api/visibility \ -f visibility='public'
-
Kubernetes Setup
# 1. Create namespace kubectl create namespace threads-bot # 2. Create secrets kubectl create secret generic threads-bot-secrets \ --from-file=.env.production \ --namespace threads-bot # 3. Apply configurations kubectl apply -f k8s/ --namespace threads-bot
-
SSL/TLS Configuration
# 1. Install cert-manager kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml # 2. Create ClusterIssuer kubectl apply -f k8s/cert-issuer.yaml # 3. Verify certificates kubectl get certificates -n threads-bot
-
Local Development
# 1. Start with monitoring docker compose -f docker-compose.yml \ -f docker-compose.monitor.yml \ up -d # 2. Watch logs docker compose logs -f api # 3. Access services open http://localhost:3000 # API open http://localhost:3001 # Grafana open http://localhost:9090 # Prometheus
-
Preview Environments
# 1. Create preview ./scripts/deploy.sh --env preview --pr 123 # 2. Cleanup preview ./scripts/cleanup-preview.sh --pr 123
-
Manual Deployment
# 1. Build and tag docker build -t ghcr.io/username/threads-bot-api:v1.0.0 . docker push ghcr.io/username/threads-bot-api:v1.0.0 # 2. Deploy DEPLOY_ENV=production ./scripts/deploy.sh --version v1.0.0 # 3. Verify kubectl get pods -n threads-bot kubectl logs -f deployment/threads-bot -n threads-bot
-
Automated Release
# 1. Create and push tag git tag v1.0.0 git push origin v1.0.0 # 2. Monitor workflow gh workflow view release # 3. Verify deployment ./scripts/verify-deployment.sh v1.0.0
-
Automatic Rollback
# Triggered on failed health checks ./scripts/rollback.sh --last-stable
-
Manual Rollback
# To specific version ./scripts/rollback.sh --version v0.9.0 # Verify rollback curl https://your-domain.com/health
-
API Overview Dashboard
{ "title": "API Overview", "panels": [ { "title": "Request Rate", "type": "graph", "datasource": "Prometheus", "targets": [ { "expr": "rate(http_requests_total[5m])" } ] }, { "title": "Error Rate", "type": "graph", "targets": [ { "expr": "rate(http_errors_total[5m])" } ] }, { "title": "Response Time", "type": "gauge", "targets": [ { "expr": "http_request_duration_seconds" } ] } ] }
-
Tunnel Status Dashboard
{ "title": "Tunnel Status", "panels": [ { "title": "Tunnel Uptime", "type": "stat", "targets": [ { "expr": "tunnel_uptime_seconds" } ] }, { "title": "Connection Issues", "type": "timeseries", "targets": [ { "expr": "tunnel_connection_errors_total" } ] } ] }
-
API Alerts
groups: - name: api rules: - alert: HighErrorRate expr: rate(http_errors_total[5m]) > 0.1 for: 5m labels: severity: critical - alert: SlowResponses expr: http_request_duration_seconds > 2 for: 5m labels: severity: warning
-
Tunnel Alerts
groups: - name: tunnel rules: - alert: TunnelDown expr: tunnel_up == 0 for: 1m labels: severity: critical - alert: TunnelLatency expr: tunnel_latency_seconds > 1 for: 5m labels: severity: warning
-
Local Access
# Grafana open http://localhost:3001 # Default credentials: admin/admin # Prometheus open http://localhost:9090 # AlertManager open http://localhost:9093
-
Production Access
# Port forward services kubectl port-forward svc/grafana 3001:3000 -n monitoring kubectl port-forward svc/prometheus 9090:9090 -n monitoring kubectl port-forward svc/alertmanager 9093:9093 -n monitoring
-
Pull Request (
pr.yml
)- Triggered on PRs to main branch
- Runs tests, lint, and type checks using Bun
- Creates preview deployment
- Required secrets:
DOCKER_REGISTRY
-
Main Branch (
main.yml
)- Triggered on pushes to main
- Builds and deploys to production
- Runs smoke tests
- Automatic rollback on failure
- Required secrets:
DOCKER_REGISTRY
KUBECONFIG
PRODUCTION_URL
-
Security Scan (
security.yml
)- Daily security scans
- Runs on main branch pushes
- Uses Trivy and CodeQL
- Creates issues for vulnerabilities
- Required secrets: None
- Required variables:
DOCKER_REGISTRY
-
Release (
release.yml
)- Triggered on version tags (
v*
) - Creates GitHub releases
- Builds and tags Docker images
- Required secrets:
DOCKER_REGISTRY
- Triggered on version tags (
-
Monitoring (
monitor.yml
)- Runs health checks every 5 minutes
- Creates issues for failures
- Required secrets:
PRODUCTION_URL
-
GitHub Repository Secrets
# Container Registry DOCKER_REGISTRY=ghcr.io/your-username # Kubernetes Config (base64 encoded) KUBECONFIG=<base64-encoded-kubeconfig> # Production URL PRODUCTION_URL=https://your-domain.com
-
How to Get Secrets:
-
DOCKER_REGISTRY
:- Use GitHub Container Registry:
ghcr.io/username
- Or Docker Hub:
docker.io/username
- Use GitHub Container Registry:
-
KUBECONFIG
:# Encode your kubeconfig base64 -i ~/.kube/config
-
PRODUCTION_URL
: Your production domain
-
-
Deploy Script (
scripts/deploy.sh
)# Development deployment DEPLOY_ENV=development ./scripts/deploy.sh # Production deployment DEPLOY_ENV=production ./scripts/deploy.sh
Features:
- Environment validation
- Dependency checks
- Health monitoring
- Colored output
- Error handling
-
Rollback Script (
scripts/rollback.sh
)# Rollback to specific version ./scripts/rollback.sh --version v1.2.3 # Rollback to last stable ./scripts/rollback.sh --last-stable
Features:
- Version management
- Automatic backup
- Health verification
- Cleanup of old backups
-
Development Setup
# docker-compose.yml services: api: build: target: development volumes: - .:/app tunnel: profiles: ["development"]
-
Production Setup
# docker-compose.prod.yml services: api: build: target: production restart: always monitoring: image: prom/prometheus grafana: image: grafana/grafana
-
Monitoring Setup
# docker-compose.monitor.yml services: prometheus: volumes: - ./monitoring/prometheus:/etc/prometheus grafana: volumes: - ./monitoring/grafana:/etc/grafana alertmanager: image: prom/alertmanager
-
Development
# .env.development NODE_ENV=development TUNNEL_SUBDOMAIN=threads-bot-api MONITOR_ENABLED=true
-
Production
# .env.production NODE_ENV=production PRODUCTION_URL=https://your-domain.com MONITOR_ENABLED=true ALERT_WEBHOOK=https://your-webhook.com
-
Monitoring
# .env.monitor GRAFANA_ADMIN_PASSWORD=your_secure_password PROMETHEUS_RETENTION=15d ALERT_CHANNELS=slack,email
-
Setup Repository
# 1. Add GitHub Secrets gh secret set DOCKER_REGISTRY -b "ghcr.io/username" gh secret set KUBECONFIG -b "$(base64 -i ~/.kube/config)" gh secret set PRODUCTION_URL -b "https://your-domain.com" # 2. Create environments cp .env.example .env.development cp .env.example .env.production # 3. Start development DEPLOY_ENV=development ./scripts/deploy.sh
-
Monitor Deployment
# View logs docker-compose logs -f # Check metrics open http://localhost:3001 # View alerts open http://localhost:9093
This project was created using bun init
in bun v1.1.29. Bun is a fast all-in-one JavaScript runtime.