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
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"
}
Submit A -> [B, C] as one workflow:
{
"tasks": [
{"key": "a", "language": "python", "source": "def main(): return 1"},
{"key": "b", "language": "python", "source": "def main(): return 2", "depends_on": ["a"]},
{"key": "c", "language": "bash", "source": "main() { echo 3; }", "depends_on": ["a"]}
]
}
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
Third-party services run in the background with PID and log files under
~/.farfarfun/funmill/services/<service>/; replace dagu with windmill in
the lifecycle commands as needed. The Funmill API remains in the foreground and
stops with Ctrl+C. Add authentication, TLS, firewall rules, PostgreSQL
backups, callback egress restrictions, and a secrets manager before network
exposure.
Metadata
Release files for funmill-api 0.1.8
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-0.1.8-py3-none-any.whl | Python 3 | none | any | Details |
Release files / funmill_api-0.1.8-py3-none-any.whl
| Download URL | funmill_api-0.1.8-py3-none-any.whl |
|---|---|
| Size | 27.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6d301e217f512733a272127ac9a42337971b15f068b9e64f31ebf6bf05860cad
|
|
BLAKE2b-256 checksum How to use checksums |
bb52f4e77eddcf471fd9879b5371628587c4c60fea49c153cdf6975316ba2f4a
|
| 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 0.1.8
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.