Standalone Railway-deployable GovCon automation backend.
| Service | Command |
|---|---|
| Echo Web | uvicorn echo.main:app --host 0.0.0.0 --port $PORT |
| Echo Worker | python -m echo.worker |
Both services share the same codebase and the same Railway Postgres database. Deploy the Web service first (it runs create_tables() on startup).
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (injected by Railway Postgres plugin) |
ECHO_API_KEY |
Secret key for API authentication |
| Variable | Description |
|---|---|
ANTHROPIC_API_KEY |
Enables AI content generation via Claude |
ANTHROPIC_MODEL |
Claude model to use (default: claude-3-5-haiku-20241022) |
SAM_GOV_API_KEY |
SAM.gov contract opportunity search |
BUFFER_API_KEY |
Buffer social scheduling (access token) |
BUFFER_PROFILE_IDS |
Comma-separated Buffer profile ids to target (default: first connected) |
LINKEDIN_ACCESS_TOKEN |
LinkedIn publishing |
LINKEDIN_AUTHOR_URN |
LinkedIn author URN (urn:li:person:...) |
SLACK_WEBHOOK_URL |
Slack notifications |
RESEND_API_KEY + EMAIL_FROM |
Send approved email drafts via Resend (POST /approvals/{id}/send) |
GA4_PROPERTY_ID |
GA4 property id for campaign click/conversion attribution |
GA4_ACCESS_TOKEN |
OAuth2 bearer token for the GA4 Data API (read-only) |
IMAGE_API_KEY |
OpenAI-compatible images API key (auto Instagram image) |
VIDEO_API_KEY + VIDEO_API_URL |
External render service for TikTok video ({script,voice} → {video_url}) |
SUPABASE_URL + SUPABASE_SERVICE_KEY + MEDIA_BUCKET |
Host generated images at a public URL (for base64 image responses) |
ECHO_ALLOW_LIVE_PUBLISH |
Set to true to enable live publishing (default: dry-run only) |
ECHO_ENABLED |
Set to false to disable workflow scheduling (default: true) |
ECHO_SCHEDULE_<SLUG> |
Override a scheduled workflow's cadence in seconds (0 disables) |
ECHO_RLS_ENABLED |
Scope DB sessions to their tenant for opt-in RLS (default: false; pair with SELECT echo_enable_rls();) |
NRS_API_URL / SEMA_API_URL |
Extra disaster feeds folded into FEMA (safe no-op when unset) |
WORKER_TICK_INTERVAL |
Worker scheduler interval in seconds (default: 60) |
CORS_ORIGINS |
Comma-separated allowed origins (default: *) |
All endpoints except GET /api/v1/health require authentication.
Pass one of:
- Header:
x-echo-key: <ECHO_API_KEY> - Header:
Authorization: Bearer <ECHO_API_KEY>
GET /api/v1/health # Unauthenticated health check
GET /api/v1/workflows # List registered workflows
POST /api/v1/workflows/{slug}/run # Trigger a workflow
GET /api/v1/runs # List workflow runs
GET /api/v1/runs/{run_id} # Get a workflow run
GET /api/v1/approvals # List pending approvals
POST /api/v1/approvals/{approval_id}/decide # Approve or reject
POST /api/v1/govcon/approvals/{id}/send # Send an approved draft (LinkedIn/email; dry-run unless gate on)
GET /api/v1/content # Content cockpit
GET /api/v1/publishing-jobs # Publishing job cockpit
GET /api/v1/logs # Automation logs
GET /api/v1/integration-health # Integration health
GET /api/v1/analytics/summary # Aggregate analytics
Interactive docs at /docs.
| Slug | Name |
|---|---|
weekly_report |
Weekly GovCon Report |
govcon_daily_intelligence |
GovCon Daily Intelligence Briefing |
linkedin_signal_post |
LinkedIn Signal Post |
fema_disaster_monitor |
FEMA Disaster Monitor |
sam_opportunity_watch |
SAM.gov Opportunity Watch |
approved_publisher |
Approved Publisher |
content_calendar_archive |
Content Calendar Archive |
prospect_dm |
Prospect DM Generator |
strategic_comment |
Strategic Comment Generator |
social_post |
Multi-Platform Social Post (LinkedIn/Facebook/Instagram/TikTok) |
produce_media |
Produce Media Asset (Instagram image / TikTok video) |
📘 For a full, step-by-step go-live runbook (migrations, env vars, smoke checks, the publish-gate flip, troubleshooting) see DEPLOY.md.
- Connect this repo in the Railway dashboard
- Set all required env vars in the Railway service settings
- Add a Postgres plugin — Railway injects
DATABASE_URLautomatically - Deploy —
railway.jsonconfigures the start command and health check
In the Railway project, add a second service from the same repo with start command:
python -m echo.worker
Share the same env vars (no separate Postgres needed — same DATABASE_URL).
pip install -r requirements.txt
python verify_golive.pyA hermetic smoke suite drives the real FastAPI app against a throwaway SQLite database in dry-run mode (no Postgres, Railway, or live API keys needed). It covers the auth gate, workflow execution, the approval→publish lifecycle, every cockpit read endpoint, and the analytics summary.
pip install -r requirements.txt -r requirements-dev.txt
pytest -qRun in Supabase SQL Editor (or via supabase db push):
supabase/migrations/0001_echo_core_schema.sql— workflow_runs, approvalssupabase/migrations/0002_cockpit_read_models.sql— content_items, publishing_jobs, automation_logs, integration_healthsupabase/migrations/0003_echo_jobs.sql— echo_jobs, echo_job_schedules, echo_execution_auditssupabase/migrations/0004_echo_govcon.sql— echo_workflows, echo_analytics_events, echo_sturgeon_handoffs, compatibility viewssupabase/migrations/0005_id_type_reconciliation.sql— align id columns (UUID→TEXT) with the ORMsupabase/migrations/0006_multitenant_rls.sql— opt-in multi-tenant RLS (SELECT echo_enable_rls();)
All migrations are idempotent and safe to re-run.
- Secrets are read from environment variables only — never hardcoded
- Live publishing is gated behind
ECHO_ALLOW_LIVE_PUBLISH=true(default: dry-run) - The backend has no dependency on frontend code