ArchiveBox/ArchiveBox · error · HttpError

Invalid action: {action}

Error message

Invalid action: {action}

What it means

patch_crawl (PATCH /api/v1/crawl/{crawl_id}) accepts an optional 'action' field in the JSON body; only 'pause', 'resume', 'unpause', and 'cancel' are supported. Any other value raises HttpError 400 with the invalid action echoed back. This is a strict allowlist so unsupported lifecycle transitions fail fast instead of silently doing nothing.

Source

Thrown at archivebox/api/v1_crawls.py:223

@router.patch("/crawl/{crawl_id}", response=CrawlSchema, url_name="patch_crawl")
def patch_crawl(request: HttpRequest, crawl_id: str, data: CrawlUpdateSchema):
    """Update a crawl (e.g., set status=sealed to cancel queued work)."""
    crawl = get_crawl_by_ref(crawl_id)
    payload = data.dict(exclude_unset=True)
    update_fields = ["modified_at"]

    action = payload.pop("action", None)
    if action:
        if action == "pause":
            crawl.pause()
            return crawl
        if action in ("resume", "unpause"):
            crawl.resume()
            return crawl
        if action == "cancel":
            crawl.cancel()
            return crawl
        raise HttpError(400, f"Invalid action: {action}")

    tags = payload.pop("tags", None)
    tags_str = payload.pop("tags_str", None)
    if tags is not None or tags_str is not None:
        crawl.tags_str = ",".join(normalize_tag_list(tags, tags_str or ""))
        update_fields.append("tags_str")

    if "status" in payload:
        if payload["status"] not in Crawl.StatusChoices.values:
            raise HttpError(400, f"Invalid status: {payload['status']}")
        if payload["status"] == Crawl.StatusChoices.SEALED:
            crawl.cancel()
            return crawl
        crawl.status = payload["status"]
        update_fields.append("status")

    if "retry_at" in payload:
        crawl.retry_at = payload["retry_at"]

View on GitHub (pinned to 74564b2822)

Solutions

  1. Use one of the allowed actions: "pause", "resume", "unpause", or "cancel" (exact lowercase spelling)
  2. If you wanted to stop queued work permanently, send {"action": "cancel"} or set status to "sealed"
  3. Remove the 'action' field entirely if you only meant to update tags/status/retry_at fields

Example fix

// before
PATCH /api/v1/crawl/abc123 {"action": "stop"}
// after
PATCH /api/v1/crawl/abc123 {"action": "cancel"}
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED_ACTIONS = new Set(['pause', 'resume', 'unpause', 'cancel']);
if (body.action !== undefined && !ALLOWED_ACTIONS.has(body.action)) {
  throw new Error(`action must be one of ${[...ALLOWED_ACTIONS]}`);
}

Type guard

function isValidCrawlAction(a) {
  return ['pause', 'resume', 'unpause', 'cancel'].includes(a);
}

Try / catch

try {
  await patchCrawl(id, { action });
} catch (e) {
  if (e.status === 400 && /Invalid action/.test(e.message)) {
    // fall back to { status: 'sealed' } for cancel-like intent
  }
}

Prevention

When it happens

Trigger: PATCH /api/v1/crawl/{crawl_id} with body {"action": "stop"}, {"action": "Pause"} (wrong case), {"action": "start"}, or a typo like {"action": "caancel"}.

Common situations: Clients written against older API versions that allowed different action names; casing/typo mistakes; UIs sending 'restart' or 'abort' expecting them to map to cancel.

Related errors


AI-assisted analysis of ArchiveBox/ArchiveBox@74564b2822 (2026-08-28). Data as JSON: /api/errors/209667f5b6119f0a. Report an issue: GitHub.