- Package:
apps/worker - Entrypoint:
uv run --package worker worker-consumer - CLI:
uv run --package worker jobsctl
The jobsctl utility can inspect and rerun jobs by id.
Defaults:
- Base URL:
- Outside Docker:
http://localhost:8090 - Inside Docker:
http://web:8090 - Override:
$WORKER_API_BASE_URL
- Outside Docker:
- API secret:
$API_SHARED_SECRET(sent asX-API-Secret) - Timeout:
10.0seconds
Usage:
uv run --package worker jobsctl --help
uv run --package worker jobsctl status <job_id>
uv run --package worker jobsctl rerun <job_id>
uv run --package worker jobsctl recent --minutes 60
uv run --package worker jobsctl recent --minutes 60 --status queued
uv run --package worker jobsctl recent --minutes 120 --type process_webhook_event_jobExamples:
uv run --package worker jobsctl status job-123uv run --package worker jobsctl rerun job-123
uv run --package worker jobsctl recent --minutes 60
uv run --package worker jobsctl recent --minutes 120 --limit 20
uv run --package worker jobsctl recent --minutes 120 --status succeeded --type sync_people_from_crm_jobIf needed, pass overrides explicitly:
uv run --package worker jobsctl \
--api-url http://localhost:8090 \
--secret "$API_SHARED_SECRET" \
rerun job-123You can still use curl directly:
- Get job status:
curl -X GET "http://localhost:8090/jobs/<job_id>" \
-H "X-API-Secret: $API_SHARED_SECRET"- Rerun a job:
curl -X POST "http://localhost:8090/jobs/<job_id>/rerun" \
-H "X-API-Secret: $API_SHARED_SECRET"Run this from a worker container to validate webhook delivery and payload shape:
docker compose exec worker uv run --package worker python - <<'PY'
from five08.discord_webhook import DiscordWebhookLogger
DiscordWebhookLogger(
webhook_url="https://discord.com/api/webhooks/<WEBHOOK_ID>/<WEBHOOK_TOKEN>"
).send(
username="508 Workflows",
embeds=[
{
"title": "Test Alert",
"description": "Something happened.",
"color": 15158332,
"fields": [
{"name": "Environment", "value": "production", "inline": True},
{"name": "Service", "value": "web", "inline": True},
],
}
],
)
PYGET /health: Redis/Postgres/worker health check.GET /jobs/{job_id}: Fetch queued job status/result payload.GET /jobs?minutes=<n>&limit=<n>[&status=<status>][&type=<job_type>]: Fetch recent job metadata for jobs created in the last<minutes>, optionally filtered by status and/or type.POST /jobs/{job_id}/rerun: Enqueue a duplicate rerun of an existing job id.POST /jobs/resume-extract: Enqueue resume profile extraction.POST /jobs/resume-apply: Enqueue confirmed CRM field apply.POST /webhooks/{source}: Generic webhook enqueue endpoint.POST /webhooks/espocrm: EspoCRM webhook endpoint (expects array payload).POST /webhooks/espocrm/people-sync: EspoCRM contact-change webhook for people cache sync.POST /webhooks/docuseal: Docuseal agreement webhook endpoint.- After the worker marks
cMemberAgreementSignedAt, it also makes a best-effort call to the Discord bot internal API to add theMemberrole when the CRM contact has a linkedcDiscordUserID.
- After the worker marks
POST /process-contact/{contact_id}: Manually enqueue one contact skills job.POST /sync/people: Manually enqueue a full CRM->people cache sync.POST /audit/events: Persist one human audit event (discordoradmin_dashboard).GET /auth/login: Start OIDC Auth Code + PKCE login flow.GET /auth/callback: Complete OIDC callback and set HttpOnly session cookie.GET /auth/me: Return active session identity.POST /auth/logout: Clear active session cookie + server session.POST /auth/discord/links: Create one-time dashboard deep link from Discord command context.GET /auth/discord/link/{token}: Show a no-store confirmation page for a Discord dashboard deep link without consuming the token.POST /auth/discord/link/{token}/consume: Consume the one-time Discord dashboard deep link and redirect to the authenticated dashboard session.- Auth flows emit best-effort human audit events (
auth.login,auth.logout) under sourceadmin_dashboard.
Discord deep-link identity policy:
- Discord deep links are available to active CRM-linked Discord users with Steering Committee role or higher.
DISCORD_ADMIN_ROLEScontrols which Discord roles can receive admin dashboard permissions (Admin,Ownerrecommended).OIDC_ADMIN_GROUPScontrols normal OIDC dashboard admin membership (authentik Adminsrecommended).AUTH_SESSION_TTL_SECONDScontrols dashboard session lifetime after login (86400, one day, by default).DASHBOARD_PUBLIC_BASE_URLshould be set to the public dashboard origin in production, for examplehttps://workflows.508.dev, so Discord-created links use the browser-accessible host and dashboard write CSRF checks can accept the public origin when the app is behind a proxy/tunnel.DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS=true(default): Discord deep links also require OIDC email identity checks against the linked CRM/Discord dashboard user.DISCORD_LINK_REQUIRE_OIDC_IDENTITY_CHECKS=false: Discord deep links create a Discord-backed session directly after re-validating active CRM membership + Discord Steering Committee+ role, without forcing an OIDC roundtrip.- In local/dev/test only, the trusted Discord bot role context can create and consume a dashboard link when the local
peoplecache has no matching CRM-linked row. Production still requires the normal CRM/people identity. - Jobs, reruns, people sync, and audit are sensitive admin permissions and require an SSO-validated dashboard session even when the user entered through a Discord link. Local/dev/test environments allow these permissions for development.
- Worker queue configuration resolves to one effective queue via
WORKER_QUEUE_NAMES. - Job handler registration for rerun and worker execution is centralized in
five08.worker.jobs.JOB_FUNCTIONS.
Returns persisted job status and the latest result payload.
- Path params:
job_id(string): persisted job id.
Creates and enqueues a new duplicate job from the source job's original args/kwargs.
- The source job is not mutated.
- A new job row is persisted with a new
job_id. - Rerun idempotency key format:
manual-rerun:{source_job_id}:{ULID}.
Example:
curl -X POST "http://localhost:8090/jobs/<job_id>/rerun" \
-H "X-API-Secret: $API_SHARED_SECRET"Example success response (202):
{
"status": "queued",
"source_job_id": "job-old-1",
"job_id": "job-new-1",
"type": "process_docuseal_agreement_job",
"created": true
}Return recent jobs created in a rolling time window.
- Query params:
minutes(integer, default:60, minimum:1): look back window size.limit(integer, default:100, minimum:1, maximum:1000): number of rows to return.status(optional string): filter jobs by persisted status (queued,running,succeeded,failed,dead,canceled).type(optional string): filter jobs by type/function name.
Example:
curl -X GET "http://localhost:8090/jobs?minutes=120&limit=50&status=succeeded" \
-H "X-API-Secret: $API_SHARED_SECRET"Example response:
[
{
"job_id": "job-123",
"type": "process_webhook_event_job",
"status": "succeeded",
"attempts": 1,
"max_attempts": 8,
"last_error": null,
"created_at": "2026-02-26T12:00:00+00:00",
"updated_at": "2026-02-26T12:00:00+00:00"
}
]Enqueues one resume extraction job.
- JSON body:
contact_id(string, required)attachment_id(string, required)filename(string, required)
Example:
curl -X POST "http://localhost:8090/jobs/resume-extract" \
-H "X-API-Secret: $API_SHARED_SECRET" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "contact-123",
"attachment_id": "att-456",
"filename": "resume.pdf"
}'Enqueues one CRM apply job after resume update confirmation.
- JSON body:
contact_id(string, required)updates(object[string->string], required): CRM field updates.link_discord(object, optional):{ "user_id": "...", "username": "..." }
Manually enqueues one contact skills job.
- Path params:
contact_id(string, required)
Manually enqueues a full CRM -> people cache sync.
Enqueues DocuSeal agreement-signing jobs.
- Job input contract for queueing:
completed_atis a UTC string usingYYYY-MM-DD HH:mm:ss. - Example value:
2026-03-02 10:02:30. - Required payload fields:
event_typemust beform.completeddata.emailnon-empty signer emaildata.completed_ator top-leveltimestamp(ISO timestamp string)data.template.idmust match configuredDOCUSEAL_MEMBER_AGREEMENT_TEMPLATE_ID
Generic webhook enqueue endpoint.
- Path params:
source(string, required): source label written into job payload.
- JSON body:
- Any JSON object payload.
EspoCRM webhook endpoint (expects array payload).
- JSON body:
- Array of event objects, each with at least
id(string).
- Array of event objects, each with at least
EspoCRM contact-change webhook for people cache sync.
- JSON body:
- Array of event objects, each with at least
id(string).
- Array of event objects, each with at least