Crontab curl: call an API daily and escape the % sign
A crontab line runs curl with /bin/sh, and a bare % becomes a newline. Escape it as \%, use full paths, log the output, and key the request by date.

To call an API from crontab with curl, put the whole command on one crontab line and write it for cron, not for your terminal: cron runs the line with /bin/sh and turns every unescaped % into a newline, so date +%F must be written date +\%F. Use full paths such as /usr/bin/curl, append the output to a log file, and keep the API key in a file that curl reads with -H @file.
Cron facts come from cronie's crontab(5) and cron(8) man pages as published on man7.org; other cron implementations can differ. curl facts come from the curl man page, and Sume facts from Create a run, Runs and results and Errors and spend. All were read on 2026-09-28. Sume has no crontab integration: the job makes one plain HTTPS call.
What does a working crontab curl line look like?
This entry starts one Sume Format run at 6:00 a.m. every day. \% keeps the date intact, cron sets $HOME from /etc/passwd, and --fail-with-body (curl 7.76.0 and later) makes curl exit with an error on a 4xx or 5xx while still writing Sume's JSON error to the log. The header file holds one line: Authorization: Bearer followed by the key.
# m h dom mon dow command (crontab -e; one entry per line)
0 6 * * * /usr/bin/curl -sS --fail-with-body --max-time 30 --retry 3 -H @$HOME/.config/sume/auth-header -H 'Content-Type: application/json' -H "Idempotency-Key: daily-recap-$(date -u +\%F)" -d '{"input":{"feed_url":"https://example.com/daily.json"}}' https://api.sume.com/v1/formats/acme/daily-recap/runs >> $HOME/sume-cron.log 2>&1Why does curl work in my terminal but not in crontab?
Because cron's environment is not your shell's. Most failures fall into one of these rows.
| Symptom | Cause | Fix |
|---|---|---|
The command stops at date + | cron turns a bare % into a newline and sends the rest to the command's standard input | Write every % as \% |
| A tool that runs in your terminal isn't found | cronie sets its own PATH unless started with -P | Use full paths such as /usr/bin/curl |
Sume answers 401 unauthorized | The key you exported in your shell isn't set: cron itself sets SHELL, LOGNAME and HOME | Read the key from a file with -H @file |
Sume answers 415 unsupported_media_type | -d sends application/x-www-form-urlencoded | Add -H 'Content-Type: application/json' |
| The job exits 0 but nothing started | By default curl does not treat HTTP error codes as a failure | Add --fail-with-body and read the log |
| No output anywhere | cron mails command output to the crontab's owner | Append >> file 2>&1 |
| The last entry misbehaves | Every entry must end in a newline, or cron considers the crontab at least partially broken | End the file with a newline |
How do I keep the API key out of the crontab?
Put the header in a file that only your user can read. -H @file makes curl add a header for each line of the file, so the key appears neither in the crontab nor on the command line; curl's manual, writing about passwords, says such data should be read from a file and never used in clear text in a command line. curl bearer token: how to send the Authorization header covers the header itself. Sume's docs keep API keys on trusted servers and say to rotate a key that appears in logs or chat history.
How do I stop a double run from paying twice?
Build the Idempotency-Key from the date, as the line above does, and keep the body the same all day. A second run on the same date, or a curl retry, then gets 200 with the original run and idempotency_hit: true: no second run, no second charge. Kubernetes CronJob concurrency policy for paid API jobs lists the other replay cases.
--retry 3resends after a timeout or an HTTP408,429,500,502,503,504,522or524. It waits 1 second and then doubles the wait, and it followsRetry-After. The date key makes those resends safe.- Changing the body under the same key is
409 idempotency_conflict, and nothing runs. A deliberate second video on the same day needs a new key.
Should the cron job wait for the video?
No. The create answers immediately with a receipt, and the run itself takes minutes. Add communication.webhook_url to the body and Sume POSTs one signed format.run.terminal receipt when the run completes or fails; otherwise poll status_url from a separate job, doubling the gap up to a minute. If nothing but the clock triggers the work, Sume's own Scheduled runs a saved automation on a five-field cron in an IANA time zone: see Scheduled AI video agent runs.
Sources
Related posts
More in Integrations
- Auto run MCP tools in Cursor: Run Modes and the allowlist
Cursor auto runs an MCP tool when it is on your allowlist or your Run Mode is Run Everything. How to set it, and which tools to keep on approval.
- How to add MCP to Devin: connect Sume's hosted server
Add a custom MCP server to Devin in Customize > MCPs: HTTP transport, Sume's hosted MCP URL, an Authorization header or OAuth, then Test tools.
- Dify MCP client: connect Sume's hosted MCP server
Dify connects to remote MCP servers from Integrations > Tools. Add Sume's hosted MCP by URL, then sign in with OAuth or send an API-key header.
- Discord webhook send file: attach a video with files[0]
Yes: POST multipart/form-data to the webhook URL with the file as files[0] and the text in payload_json. Over 20 MiB by default, post the link.
Written by Sume