artur
artur is a universal config-driven Rust HTTP gateway and package orchestrator.
It lets developers expose HTTP endpoints from TOML and attach each endpoint to a controlled action. The core server stays generic: it does not hardcode challenges, wallets, blockchains, databases, queues, or business workflows. Those belong in commands or scripts that artur starts from configuration.
Or run the container image published by GitHub Actions to GitHub Packages:
The default example listens on 127.0.0.1:46796:
What Artur does
Artur maps configured HTTP routes to generic actions under the [artur] namespace:
| Action | Purpose |
|---|---|
respond.static |
Return configured JSON for health checks, metadata, mocks, or docs endpoints. |
task.run |
Run an allowlisted task synchronously or asynchronously. |
job.get |
Read the status and result of an async task started by task.run. |
workflow.run |
Run a DAG of task, store query, store execute, and response steps with dependency-based sequential or parallel execution. |
A task can be a Python script, Rust CLI, shell script, npx command, binary in your repo, or any executable available to the service user. For distributed deployments, workflow steps can also call already-running package services through shared [transports.http.*] profiles.
Minimal configuration
= 1
[]
= "127.0.0.1"
= 46796
= 1048576
[[]]
= "hello"
= "GET"
= "/v1/hello"
= "respond.static"
[]
= 200
= { = true, = "artur" }
Run it:
Task endpoint
[[]]
= "echo"
= "POST"
= "/v1/process/echo/{name}"
= "task.run"
= "echo_json"
[[]]
= "echo_json"
= "sync"
= "python3"
= ["examples/scripts/echo.py", "--name", "{{param.name}}", "--source", "{{query.source}}"]
= 30000
= "json"
[]
= "request_json"
Call it:
The task receives a request JSON document on stdin:
Async task endpoint
[[]]
= "long_task"
= "POST"
= "/v1/process/long-task"
= "task.run"
= "long_task"
[[]]
= "long_task"
= "async"
= "python3"
= ["examples/scripts/long_task.py"]
= 60000
= "json"
[]
= "body"
[[]]
= "get_job"
= "GET"
= "/v1/jobs/{job_id}"
= "job.get"
Starting the task returns a job ID:
Then read it:
Current jobs are stored in memory. Use Bria or another durable service when you need durable queues, retries, distributed workers, or long-lived state.
Template variables
Artur renders templates inside task args, env values, working directories, HTTP step URLs/headers/bodies, and stdin.type = "template" payloads.
| Template | Value |
|---|---|
{{method}} |
HTTP method. |
{{uri}} |
Full request URI. |
{{path}} |
Request path without query string. |
{{body}} |
Raw request body as text. |
{{request_json}} or {{request}} |
Full request context as compact JSON. |
{{param.name}} |
Path parameter, such as {name}. |
{{query.name}} |
Query string value. |
{{header.name}} |
Header value, lower-case lookup. |
{{env.NAME}} |
Environment variable from the Artur service process. |
{{body_json.user.id}} |
Field inside a JSON request body. Array indexes are supported, such as {{body_json.items.0}}. |
Unknown template keys render as an empty string.
Task stdin modes
[]
= "none"
[]
= "body"
[]
= "request_json"
[]
= "template"
= "user={{body_json.user.id}}"
Universal shared configuration
Artur now accepts the same universal root sections used by bria, ladon, oracles, and pano. Shared profiles live at the root; Artur-owned runtime definitions live under [artur]. Other package namespaces are intentionally ignored by Artur so the same Config.toml can be mounted into all five services.
= 1
[]
= "info"
= "json"
[]
= 1048576
= 30
[]
= "127.0.0.1"
= 46796
= "v1"
[]
= "sqlite"
= "sqlite://data/artur/api.sqlite3"
[]
= "sqlite"
= "sqlite://data/ladon/addresses.db"
[]
= "sqlite"
= "sqlite://data/pano/events.db"
[]
= "sqlite"
= "sqlite://data/oracles/rates.db"
[]
= "sqlite"
= "sqlite://data/bria/bria-state.db"
[]
= "ladon"
[]
= "pano"
[]
= "oracles"
[]
= "sqlite"
= "bria"
Artur intentionally does not accept root-level [[endpoints]], [[tasks]], or [server]. Package-owned configuration must live under [artur], so the same file can also contain [bria], [ladon], [oracles], and [pano] without ambiguity.
Workflows and store operations
A workflow.run endpoint executes ready steps in parallel and waits for declared depends_on steps before continuing. Each step result is available to later steps through {{steps.<id>...}}, so task output can become SQL parameters or another task's input.
[[]]
= "create_space"
= "POST"
= "/v1/spaces"
= "workflow.run"
[]
= false
= { = true, = "{{steps.sid.json.sid}}", = "{{steps.oracles_prices.json.prices}}", = "{{steps.bria_paid_task.json.job_id}}" }
[[]]
= "sid"
= "task"
= "sid_create"
[[]]
= "insert"
= "store.execute"
= "artur"
= ["sid"]
= "INSERT INTO spaces (sid, payload) VALUES (?1, ?2)"
= ["{{steps.sid.json.sid}}", "{{request_json}}"]
[[]]
= "lookup"
= "store.query"
= "artur"
= ["insert"]
= "SELECT sid, payload FROM spaces WHERE sid = ?1"
= ["{{steps.sid.json.sid}}"]
Use type = "http.request" when the other package is already running in another container or on another server:
[]
= "http://ladon:4010/v1"
= 10000
= { = "Bearer {{env.LADON_API_KEY}}" }
[[]]
= "ladon_addresses"
= "http.request"
= "ladon"
= "POST"
= "/addresses/checkout"
= ["sid"]
= { = "{{steps.sid.json.sid}}", = ["evm", "solana", "btc"] }
HTTP step output is available as {{steps.<id>.status}}, {{steps.<id>.body}}, and {{steps.<id>.json...}}.
Use examples/universal-composition.toml for the full five-package demonstration: create a sid, retrieve addresses from ladon, track in pano, read token and coin USD prices from oracles, store the combined record, and launch paid work through bria.
examples/compose.yaml shows the same Config.toml mounted into artur, ladon, pano, oracles, and bria containers.
Ready-to-use paid job service
The repository also includes examples/service.toml and docker-compose.yml for an end-to-end service that uses one shared config to create space ULIDs, top up per-chain token balances at a supplied current USD token rate, run immediate or async jobs, and return HTTP 402 x402-native payment requirements when the selected space balance is insufficient.
Clients can either top up the space first or submit an x-payment header for a specific job request.
Endpoint security
Security guards are declared per endpoint. Guards run before the endpoint action and participate in failed-request blocking.
[]
= "{{header.authorization}}"
= 5
= 300
= 900
[]
= "altcha_verify"
= "verified"
[]
= "x402_verify"
= "paid"
Security tasks are normal Artur tasks. They should return a successful exit code and JSON with ok, allowed, verified, or paid set to true, or use success_path to name the exact boolean field.
Challenge and space example
examples/challenge-space.toml shows how to model this kind of flow without hardcoding it into Artur:
POST /v1/challengeruns an externalchallenge create ...command.POST /v1/spaceruns your application script, which can callchallenge verify ..., allocate a randomsid, assign resources, persist data, and return JSON.GET /v1/space/{sid}andGET /v1/space/delegate lookup to your application script.
This is only an example of how developers can work with Artur. ALTCHA-style challenges, wallets, blockchains, balances, deposits, and expenses are application concerns, not Artur core concepts.
If your preferred challenge crate exposes a binary named challenge, this kind of TOML can call it directly:
[[]]
= "challenge_create"
= "sync"
= "challenge"
= [
"create",
"--cost", "5000",
"--random-counter",
"--expires-in", "600",
"--hmac-secret", "{{env.ARTUR_CHALLENGE_HMAC_SECRET}}",
"--hmac-key-secret", "{{env.ARTUR_CHALLENGE_HMAC_KEY_SECRET}}",
]
= "json"
The important part is that a developer can swap this for any other implementation by changing TOML, not recompiling Artur.
Security and operations notes
- Treat the TOML file as privileged code. Anyone who can edit it can define which commands Artur runs.
- Prefer absolute command paths in production.
- Keep
timeout_mssmall and specific per task or HTTP step. - Avoid placing secrets in command-line args because they may be visible in process listings. Prefer env vars and short-lived credentials.
- Run Artur as an unprivileged service user.
- Use a reverse proxy for TLS, authentication, rate limiting, and request logging when exposing Artur outside localhost.
- Use async task mode for short-lived background work and Bria or another durable queue when results must survive restarts.
Development
Prerequisites:
- Rust stable matching
rust-versioninCargo.toml. - Node.js/npm for the JavaScript and local
npxe2e coverage. - Docker for container build verification.
Run the same core checks as CI:
The e2e test suite starts the compiled artur binary and verifies configured static endpoints plus task endpoints backed by JavaScript (node), local npx, and a compiled Rust helper.
Docker
The included Dockerfile builds a minimal runtime image with the artur binary and example configuration. The container uses examples/docker.toml, which binds to 0.0.0.0:46796 for Docker port publishing.
GitHub Actions builds the image on pull requests and publishes it to GitHub Packages (ghcr.io/melonask/artur) on pushes to the default branch and on releases.
License
- MIT license, in
LICENSE