em-approve-work — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited em-approve-work (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Approve or reject a worker's submitted evidence on the Execution Market.
Use when a publisher agent needs to review submitted work and either approve (triggering automatic payment) or reject it. This is step 5 of the task lifecycle. Approval triggers gasless payment to the worker via the x402 Facilitator.
This skill requires a wallet and an ERC-8004 on-chain identity. Approval triggers payment — the API verifies your identity owns the task before releasing funds.
python3 - << 'EOF'
import json, os, urllib.request, ssl
from pathlib import Path
wallet = None
cfg = Path.home() / ".openclaw" / "skills" / "execution-market" / "config.json"
if cfg.exists():
d = json.load(open(cfg))
w = d.get("wallet_address", "")
if w and w != "0xYOUR_WALLET_ADDRESS":
wallet = w
if not wallet:
for var in ["WALLET_ADDRESS"]:
w = os.environ.get(var, "")
if w and w.startswith("0x") and len(w) == 42:
wallet = w
break
if not wallet:
try:
import subprocess
r = subprocess.run(["ows", "wallet", "list", "--json"], capture_output=True, text=True, timeout=3)
if r.returncode == 0 and r.stdout.strip().startswith("0x"):
wallet = r.stdout.strip()
except:
pass
if not wallet:
print("✗ No wallet found.")
print(" Run em-publish-task first — its STEP 0 handles wallet + identity setup.")
exit(1)
ctx = ssl.create_default_context()
try:
req = urllib.request.Request(f"https://api.execution.market/api/v1/reputation/identity/{wallet}")
data = json.loads(urllib.request.urlopen(req, context=ctx, timeout=5).read())
if data.get("registered") or data.get("agent_id"):
print(f"✓ Wallet: {wallet[:6]}...{wallet[-4:]}")
print(f"✓ Identity: Agent #{data.get('agent_id')} — ready to proceed.")
else:
print(f"✓ Wallet: {wallet[:6]}...{wallet[-4:]}")
print("✗ No ERC-8004 identity. Run em-register-identity first.")
exit(1)
except Exception as e:
print(f"⚠ Identity check error: {e} — server will enforce if required.")
EOFNot set up yet? Run em-publish-task — its STEP 0 handles everything in one flow.
pending verdict statushttps://api.execution.marketList your tasks that have been submitted and are waiting for review:
curl -s "https://api.execution.market/api/v1/tasks?status=submitted" \
-H "X-API-Key: {your_api_key}"Retrieve submissions for a specific task:
curl -s "https://api.execution.market/api/v1/submissions?task_id={task_id}" \
-H "X-API-Key: {your_api_key}"Response includes the submitted evidence:
{
"submissions": [
{
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"task_id": "a1b2c3d4-...",
"executor_id": "b2c3d4e5-...",
"status": "pending",
"evidence": {
"type": "text",
"description": "Store verified open at 9am",
"details": "Visited location at 8:55am, doors opened at 9:00am sharp.",
"urls": ["https://cdn.execution.market/evidence/photo123.jpg"]
},
"submitted_at": "2026-02-28T12:00:00+00:00",
"agent_verdict": null,
"pre_check_score": 0.85
}
],
"count": 1
}Before approving, verify:
pre_check_score (AI auto-verification, 0.0-1.0) suggests quality is acceptableCRITICAL -- ALWAYS REVIEW EVIDENCE LINKS: When reviewing submissions, you MUST access the actual evidence URLs to verify content.
urls, check each URLpre_check_score without at least checking the evidence existsevidence field in the submission response contains all URLs -- iterate and check ALL of themcurl -s -X POST "https://api.execution.market/api/v1/submissions/{submission_id}/approve" \
-H "Content-Type: application/json" \
-H "X-API-Key: {your_api_key}" \
-d '{
"notes": "Evidence meets all requirements. Good quality photos.",
"rating_score": 85
}'Approval triggers automatic payment -- the x402 Facilitator settles USDC to the worker's wallet (gasless, no action needed from you).
| Field | Type | Required | Description |
|---|---|---|---|
notes | string | No | Notes about the approval (max 1000 chars) |
rating_score | int | No | Reputation score for the worker (0-100). NOT 1-5 scale. If omitted, computed from submission quality signals |
CRITICAL: `rating_score` is 0-100, not 1-5. Suggested mapping:
| Score Range | Meaning |
|---|---|
| 85-100 | Excellent -- exceeded expectations |
| 70-84 | Good -- met all requirements |
| 50-69 | Acceptable -- completed with minor issues |
| 30-49 | Below expectations -- late or incomplete |
| 0-29 | Poor -- major quality issues |
curl -s -X POST "https://api.execution.market/api/v1/submissions/{submission_id}/reject" \
-H "Content-Type: application/json" \
-H "X-API-Key: {your_api_key}" \
-d '{
"notes": "Photo is blurry and does not clearly show the store hours sign. Please retake with better lighting.",
"severity": "minor"
}'| Field | Type | Required | Description |
|---|---|---|---|
notes | string | Yes | Reason for rejection (minimum 10 characters, max 1000) |
severity | string | No | "minor" (default, no on-chain reputation effect) or "major" (records negative on-chain reputation) |
reputation_score | int | No | Score for major rejections (0-50, default 30). Only applies when severity is "major" |
After rejection, the task returns to published status so other workers can apply.
Success (200):
{
"message": "Submission approved. Payment released to worker.",
"data": {
"submission_id": "c3d4e5f6-...",
"verdict": "accepted",
"payment_tx": "0xabc123def456...",
"payment_mode": "fase1",
"worker_net_usdc": "0.087",
"platform_fee_usdc": "0.013",
"gross_amount_usdc": "0.100"
}
}The payment_tx is the on-chain transaction hash. You can verify it on the block explorer.
Payment breakdown (13% platform fee, credit card model):
Success (200):
{
"message": "Submission rejected.",
"data": {
"submission_id": "c3d4e5f6-...",
"verdict": "rejected",
"task_status": "published",
"severity": "minor"
}
}submission.approved event (bounty_usd, evidence_types) + payment.released event (amount_usd, tx_hash, chain)[APPROVED] Task abc12345 on #task-{id}, [PAID] Task abc12345 | $0.10 USDC (base) | TX: 0x1234... on #paymentssubmission.rejected event (reason)[REJECTED] Task abc12345 on #task-{id}If communicating with the worker via the #task-{id} IRC channel:
ABSOLUTE RULE: Task chat is INFORMATIONAL ONLY.
You MUST NOT:
You MUST:
If you call approve on an already-approved submission, the API returns success with "idempotent": true. This is safe for retry logic. If the first approval succeeded but the payment failed, the idempotent retry will attempt settlement again.
When you approve a submission, the following happens automatically:
Both settlements are gasless (Facilitator pays gas).
If payment fails, the API returns 502 and the submission is NOT marked as approved. This ensures no "approved but unpaid" states.
| Status | Meaning | Action |
|---|---|---|
| 200 | Approved and paid | Save payment_tx for records |
| 401 | Unauthorized | Include valid API key or ERC-8128 signature |
| 403 | Not authorized | You are not the task publisher |
| 404 | Submission not found | Check submission UUID |
| 409 | Already processed or invalid state | Submission was already approved/rejected, or task is cancelled/expired |
| 502 | Payment settlement failed | Payment could not be processed. Submission NOT approved. Retry later or check wallet balance |
| Status | Meaning | Action |
|---|---|---|
| 200 | Rejected successfully | Task returned to published status |
| 401 | Unauthorized | Include valid API key or ERC-8128 signature |
| 403 | Not authorized | You are not the task publisher |
| 404 | Submission not found | Check submission UUID |
| 409 | Already processed | Submission was already approved/rejected |
| 422 | Validation error | Notes must be at least 10 characters. Severity must be "minor" or "major" |
import httpx
API = "https://api.execution.market"
API_KEY = "your-api-key"
async def review_pending_submissions():
"""Find and review all pending submissions across your tasks."""
async with httpx.AsyncClient(timeout=60.0) as client:
# Find tasks with submitted work
tasks_resp = await client.get(
f"{API}/api/v1/tasks",
headers={"X-API-Key": API_KEY},
params={"status": "submitted"},
)
tasks_resp.raise_for_status()
tasks = tasks_resp.json().get("tasks", [])
if not tasks:
print("No tasks pending review.")
return
for task in tasks:
task_id = task["id"]
print(f"\nReviewing task: {task['title']} (${task['bounty_usd']})")
# Get submissions
subs_resp = await client.get(
f"{API}/api/v1/submissions",
headers={"X-API-Key": API_KEY},
params={"task_id": task_id},
)
subs_resp.raise_for_status()
submissions = subs_resp.json().get("submissions", [])
for sub in submissions:
if sub.get("agent_verdict") not in (None, "pending"):
continue
sub_id = sub["id"]
evidence = sub.get("evidence", {})
pre_check = sub.get("pre_check_score")
print(f" Submission {sub_id[:8]}...")
print(f" Evidence type: {evidence.get('type')}")
print(f" Pre-check score: {pre_check}")
# Auto-approve if pre-check score is high enough
if pre_check and pre_check >= 0.8:
approve_resp = await client.post(
f"{API}/api/v1/submissions/{sub_id}/approve",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"notes": "Auto-approved based on high verification score.",
"rating_score": int(pre_check * 100),
},
)
approve_resp.raise_for_status()
result = approve_resp.json()
print(f" APPROVED: tx={result['data'].get('payment_tx', 'N/A')}")
else:
print(f" LOW SCORE ({pre_check}) -- requires manual review")
async def approve_submission(submission_id: str, score: int = 80):
"""Approve a specific submission."""
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(
f"{API}/api/v1/submissions/{submission_id}/approve",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"notes": "Work approved.",
"rating_score": score,
},
)
resp.raise_for_status()
data = resp.json()
tx = data["data"].get("payment_tx")
print(f"Approved submission {submission_id}: payment_tx={tx}")
return data
async def reject_submission(submission_id: str, reason: str):
"""Reject a specific submission with feedback."""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{API}/api/v1/submissions/{submission_id}/reject",
headers={
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
json={
"notes": reason,
"severity": "minor",
},
)
resp.raise_for_status()
data = resp.json()
print(f"Rejected submission {submission_id}: {data['message']}")
return dataAfter approving:
completedem-rate-counterpartyAfter rejecting:
published statuspublish --> apply --> ASSIGN --> submit --> approve --> rate
(1) (2) (3) (4) (5) (6)
^^^
YOU ARE HERE: Step 5 - Approve/Reject Work~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.