Funmill
Funmill provides one stable task API while execution is delegated to a
replaceable backend. The recommended local backend is self-hosted
Dagu, pinned to v2.16.3. Windmill remains
available for existing deployments.
client -> Funmill /v1 -> TaskBackend -> Dagu
-> Windmill
Funmill owns the public request and response models. Backend job IDs remain opaque strings, and no backend-specific routes or payloads are exposed to clients.
Start
Install the project and the Dagu binary. The installer supports macOS and Linux on Intel/AMD and ARM64:
uv sync
uv run funmill install dagu
Start Dagu in the background. It stores state under
~/.farfarfun/funmill/services/dagu/data/ and needs no external database:
uv run funmill start dagu
The command reports its PID and log path. Open http://localhost:8813, then start Funmill:
FUNMILL_API_KEY='replace-me' \
FUNMILL_BACKEND=dagu \
DAGU_URL='http://127.0.0.1:8813' \
uv run funmill start
Like Dagu and Windmill, funmill start runs the API in the background and
reports its PID and log path immediately. Use uv run funmill run instead to
keep it in the foreground (stop with Ctrl+C), which is convenient for local
debugging.
Managed HTTP services bind to 0.0.0.0: Funmill uses port 8812 and the active
third-party service uses 8813. Local client URLs still use 127.0.0.1 or
localhost; 0.0.0.0 is a listen address, not a client destination.
Dagu starts without authentication. Because it listens on every interface,
restrict port 8813 with a firewall or enable Dagu authentication. When
authentication is enabled, set DAGU_TOKEN for both funmill start dagu and
funmill start so cross-run dependencies can query Dagu from worker processes.
Dagu runs submitted source with the service user's host permissions. Keep both services private and accept only trusted code; use isolated workers or containers before accepting untrusted jobs.
The Funmill API port is fixed at 8812; the active third-party UI/API port is
fixed at 8813. OpenAPI docs are at http://localhost:8812/docs. All /v1
routes require X-API-Key. See the
Dagu deployment guide or the
Windmill deployment guide for
backend-specific setup.
Run the end-to-end task and DAG checks with:
FUNMILL_API_KEY=the-value-from-env ./scripts/smoke.sh
Set FUNMILL_CALLBACK_URL to test callbacks. The URL must be reachable from
the backend workers.
API
| Operation | Route |
|---|---|
| Health check | GET /health |
| Submit Python/Bash | POST /v1/tasks |
| Submit a DAG | POST /v1/workflows |
| Status | GET /v1/tasks/{task_id} |
| Logs | GET /v1/tasks/{task_id}/logs |
| Progress | GET /v1/tasks/{task_id}/progress |
| Result | GET /v1/tasks/{task_id}/result |
| Cancel | POST /v1/tasks/{task_id}/cancel |
| Rerun | POST /v1/tasks/{task_id}/rerun |
GET /health is unauthenticated and reports whether the Funmill process and
its configured backend are reachable; it returns {"status": "ok", "backend": "..."} on success and a 503 with a detail message when the backend is
unreachable or misconfigured.
Submit one task, optionally waiting for existing task IDs:
{
"language": "python",
"source": "def main(value: int):\n return value * 2\n",
"args": {"value": 21},
"depends_on": ["EXISTING_TASK_ID"],
"dependency_timeout_seconds": 3600,
"retry": {"attempts": 2, "delay_seconds": 5},
"timeout_seconds": 300,
"callback_url": "https://example.internal/task-callback",
"name": "double-the-value",
"description": "Doubles the input for the nightly report"
}
Submit A -> [B, C] as one workflow:
{
"tasks": [
{"key": "a", "language": "python", "source": "def main(): return 1", "name": "seed"},
{"key": "b", "language": "python", "source": "def main(): return 2", "depends_on": ["a"]},
{"key": "c", "language": "bash", "source": "main() { echo 3; }", "depends_on": ["a"]}
]
}
name/description are optional, purely for telling tasks apart (in batch
submissions too), and are not persisted by Funmill itself — Funmill has no
database. They are stored only if the configured backend itself stores them,
and GET /v1/tasks/{task_id} echoes them back only when the backend actually
returned them; otherwise both fields are null. Dagu round-trips both for
single-task submissions (they land on the DAG step and are visible in Dagu's
own UI too); for workflow submissions each task's name/description still
reaches its own step, but GET /v1/tasks/{task_id} reports the workflow run
as a whole and therefore leaves name/description as null. Windmill shows
name as the job/branch summary in its UI but does not round-trip either
field back through the API.
Dependencies inside a workflow are task keys. Top-level depends_on values are
IDs returned by earlier Funmill submissions. Backends check those dependencies
from worker jobs, so waiting does not hold the Funmill API process. Failure or
cancellation of a dependency fails the waiting task.
Workflow results and callback payloads are objects keyed by every workflow task
key. Each topological layer is a synchronization barrier; tasks in the same
layer run in parallel.
Callbacks contain task_id, status, and payload; success and failure
delivery retry three times. Delivery is at least once, so callback receivers
must be idempotent.
Python SDK
A Python client for the /v1 routes plus /health lives in the separate
funmill-sdk repository, published
to PyPI as funmill (imported as funmill.client):
pip install funmill
from funmill.client import FunmillClient, TaskSubmit
with FunmillClient(base_url="http://127.0.0.1:8812", api_key="replace-me") as client:
client.health() # {"status": "ok", "backend": "dagu"}
accepted = client.submit_task(
TaskSubmit(language="python", source="def main(): return 21 * 2")
)
task = client.get_task(accepted.task_id)
result = client.get_result(accepted.task_id)
See the funmill-sdk README for the full client API and error handling.
Backends
The public contract is TaskBackend in src/funmill/api/backends/base.py. Backend
selection uses FUNMILL_BACKEND; registration lives in
src/funmill/api/backends/__init__.py, following the same driver pattern as
fundrive. Each third-party adapter lives in its own directory, such as
src/funmill/api/backends/windmill/.
Every third-party adapter directory must include a README.md covering its
supported platforms, installation, configuration, startup, verification, and
security or operational constraints.
Every managed HTTP service must bind to SERVICE_BIND_HOST (0.0.0.0). Every
third-party service must use the shared background lifecycle in
src/funmill/api/backends/service.py and expose start, stop, and status;
restart is composed from stop and start by the CLI.
Changing the backend does not change /v1, but it does not migrate old jobs or
their IDs. Add a Funmill-owned ID mapping database only when jobs must remain
queryable after a live backend migration.
Operations
funmill services
funmill install dagu
funmill start dagu
funmill status dagu
funmill restart dagu
funmill stop dagu
funmill install windmill
funmill start windmill
funmill start
funmill status
funmill restart
funmill stop
funmill run
Every managed service, including Funmill's own API, runs in the background
with PID and log files under ~/.farfarfun/funmill/services/<service>/;
start/status/restart/stop default to the api service when no service
name is given, and dagu/windmill work the same way by passing that name
explicitly. funmill run starts the API in the foreground instead (stop with
Ctrl+C), for local debugging. Add authentication, TLS, firewall rules,
PostgreSQL backups, callback egress restrictions, and a secrets manager before
network exposure.
Metadata
Release files for funmill-api 1.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| funmill_api-1.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Release files / funmill_api-1.0.6-py3-none-any.whl
| Download URL | funmill_api-1.0.6-py3-none-any.whl |
|---|---|
| Size | 28.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a8fa5e718b8f393a49bede06382dbfe480da54e2ff14e1ea3ed132957b2f226c
|
|
BLAKE2b-256 checksum How to use checksums |
8b43c87ee3aab39a63e96dcece5ac9eb841317f6dc5a10080bd5c1d1c4e72dd1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for funmill-api 1.0.6
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.