Automation and local API
Let files move without anyone clicking: a folder that sends whatever is dropped into it, a script that runs on every arrival, and an HTTP API for your own programs. All of it runs on your computers; nothing needs a server of ours.
Which one for what#
| You want to… | Use | Needs |
|---|---|---|
| Send from a script, a build or cron | The lanshare command (exit codes, --json) | Free |
| Ask an AI assistant to send things | MCP | Free |
| Send everything dropped into a folder | Hot folders: lanshare automate | Team |
| Process files as soon as they arrive | on_receive | Team |
| Send from your own program over HTTP | Local API: lanshare api | Team |
| Tell another system about every transfer | Webhooks, audit log, syslog | Business |
Hot folders, on_receive and the local API are set in the organisation's policy.json and work on computers with a Team or Business subscription (see LAN Share for business). Check what is in force with lanshare policy show. A computer without the subscription ignores those parts of the policy. The full edition counts as Business; the Mac App Store version has none of them (editions).
Hot folders#
A hot folder is watched; whatever lands in it is sent to a computer or a group, then moved aside. Typical uses: a scanner that saves into a folder, an export from accounting software, a render queue.
{
"hot_folders": [
{
"path": "/Users/Shared/Outbox/Accounting",
"to": "Accounting",
"after": "move",
"message": "From the scanner"
},
{ "path": "/srv/exports/nightly", "to": "Backup NAS", "after": "delete" }
]
}| Field | Description |
|---|---|
pathrequired | The folder to watch. Created if missing. |
torequired | A group name (every host in it), or a saved computer's name or address. |
after | move (default): into a Sent folder inside the hot folder. delete: removed. keep: left in place. |
message | The note sent with each transfer. |
Then run the watcher, and keep it running (a login item, a launchd agent, a systemd service, a scheduled task at log-on):
$ lanshare automate # watches until stopped $ lanshare automate --once # sends what is there now and stops (for cron / Task Scheduler)
Watching /Users/Shared/Outbox/Accounting → Accounting → /Users/Shared/Outbox/Accounting/invoice-118.pdf → Anna's MacBook: waiting for approval… ✓ /Users/Shared/Outbox/Accounting/invoice-118.pdf → Anna's MacBook (184.3 KiB)
How items are picked up#
- Each file or folder at the top of the hot folder is one transfer. A folder goes as a whole.
- An item is sent only once it has stopped changing: the same size and time on two scans 3 seconds apart. A copy that is still running is never sent half-way. (With
--onceeverything present is sent right away.) - Skipped: the
Sentfolder, hidden files (starting with.), Office lock files (~$…), and downloads in progress (.part,.crdownload,.download,.tmp). The exclude list from the settings applies inside folders. - Each receiver is asked as usual, unless it trusts this computer. Pair the computers involved (or add a trusted sender) for fully unattended delivery.
- If sending fails (declined, offline), the item stays and is tried again after a minute. For a group, every host is tried, one after another; the retry goes only to the hosts that did not get it yet (remembered in
hot-folders.jsonnext to the settings, so also by the next--once). An item that has changed meanwhile goes to all of them again. - With
after: "keep"an item stays in place and is sent again only when it changes, also between runs of--once(what was sent is remembered inhot-folders.json).
A command on arrival#
on_receive runs a program after a transfer has been received and saved, on the receiving computer, as the user LAN Share runs as. It gets the saved items (files and folders) as arguments, and these environment variables. It runs for every completed arrival: files and folders, uploads from the phone page, and text (then with no paths after your own arguments; the text itself is not passed).
- LANSHARE_PEER
- The sender's computer name.
- LANSHARE_ADDRESS
- The sender's address.
- LANSHARE_FILES
- How many files.
- LANSHARE_BYTES
- Total size in bytes.
- LANSHARE_MESSAGE
- The sender's note (empty if none).
{ "on_receive": ["/usr/local/bin/lanshare-inbox", "--tag", "lan"] }The list is the program and its first arguments; the saved paths come after them. So the example runs /usr/local/bin/lanshare-inbox --tag lan /path/one /path/two.
#!/bin/sh # Scan what arrived; move anything suspicious aside and note it in the system log. shift 2 # drop "--tag lan" for item in "$@"; do if ! clamscan -r --no-summary "$item" >/dev/null; then mv "$item" /srv/quarantine/ logger -t lanshare "quarantined $item from $LANSHARE_PEER" fi done logger -t lanshare "received $LANSHARE_FILES files ($LANSHARE_BYTES bytes) from $LANSHARE_PEER ($LANSHARE_ADDRESS)"
- It runs only for received transfers that completed, not for declined or failed ones.
- stdin is empty. The program writes to LAN Share's own output: you see it when you run
lanshare receivein a terminal; under the app it is not shown anywhere, so log what you need. A non-zero exit is reported on LAN Share's error output (stderr). - A program still running after 10 minutes is stopped.
Receive rules (blocked types, size limit, allowed senders) are checked before anything is written, so on_receive only ever sees transfers the policy let in.
Local HTTP API#
lanshare api serves a small JSON API on 127.0.0.1 only, for scripts and programs on the same computer. Every call needs a bearer token.
$ export LANSHARE_API_TOKEN="$(openssl rand -hex 16)" # 32 hex digits $ lanshare api
LAN Share API on http://127.0.0.1:45822 (Authorization: Bearer 3f9c…)
| Setting | Where it comes from, first found wins |
|---|---|
| Port | --port, then api.port in the policy, then 45822. |
| Token | --token, then the LANSHARE_API_TOKEN environment variable, then api.token in the policy; otherwise a new random token (32 hex digits), printed at start. |
{ "api": { "port": 45822, "token": "9f2c4e7a1b3d5f60718293a4b5c6d7e8" } }Use your own random token (openssl rand -hex 16), at least 16 characters.
Endpoints#
| Call | Body | Answers |
|---|---|---|
GET /v1/status | This computer: name, port, receiving, receive folder (as status) | |
GET /v1/hosts | Saved hosts with online checked (as list_hosts) | |
GET /v1/history?limit=50 | The newest transfers, default 50 (as history) | |
POST /v1/send | {"to", "paths", "message"} | {"sent": [ … ]}, one entry per receiver |
POST /v1/send_text | {"to", "text"} | What was sent (as send_text) |
tois a group name (every host in it, one after another) or a host.pathsare absolute paths on this computer;messageis optional.- A send answers when it is done, which includes the receiver's decision (up to 3 minutes): give your HTTP client a long enough timeout.
- Compression and the exclude list come from the settings.
- Status codes:
200with the result;400with{"error": "…"}when the call failed (declined, unknown host, bad JSON);401without the right token;404for any other path. - A group send stops at the first host that fails and answers
400with its error; the hosts before it already got the files, so check the history before sending again. - A body over 1 MiB is not read: the connection is closed without an answer.
With curl#
$ curl -s -H "Authorization: Bearer $LANSHARE_API_TOKEN" http://127.0.0.1:45822/v1/status $ curl -s -H "Authorization: Bearer $LANSHARE_API_TOKEN" http://127.0.0.1:45822/v1/hosts | jq '.[] | {name, online}'
$ curl -s --max-time 900 \
-H "Authorization: Bearer $LANSHARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to": "QA", "paths": ["/Users/you/builds/app-214.dmg"], "message": "Build 214"}' \
http://127.0.0.1:45822/v1/send{"sent":[{"target":"QA Mac mini","address":"192.168.1.60","items":"app-214.dmg","files":1,"bytes":88604672,"size":"84.5 MiB","compressed":false,"wire_bytes":88604672,"skipped_bytes":0,"seconds":1.4}]}$ curl -s -H "Authorization: Bearer $LANSHARE_API_TOKEN" \
-d '{"to": "Office iMac", "text": "Build 214 is on the QA machines"}' \
http://127.0.0.1:45822/v1/send_textHTTP/1.1 401 Unauthorized {"error":"missing or wrong token"} HTTP/1.1 400 Bad Request {"error":"QA Windows rejected the transfer"}
From Python#
import json, os, urllib.request
API = "http://127.0.0.1:45822"
TOKEN = os.environ["LANSHARE_API_TOKEN"]
def call(method, path, body=None):
req = urllib.request.Request(
API + path, method=method,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
)
# A send waits for the receiver to accept: allow minutes, not seconds.
with urllib.request.urlopen(req, timeout=900) as r:
return json.load(r)
online = [h["name"] for h in call("GET", "/v1/hosts") if h["online"]]
print("online:", online)
print(call("POST", "/v1/send", {"to": "Test server", "paths": ["/srv/builds/latest"]}))Anyone who has the token and can run a program on this computer can send any file this user can read. Keep the token out of shell history and repositories, give it 16 characters or more, and run lanshare api only as long as something needs it. It never listens on the network, only on 127.0.0.1.
Something missing or wrong? Write to [email protected].