Spamoor API Consumer Guide
July 8, 2026 · View on GitHub
This guide provides comprehensive documentation for consuming the Spamoor daemon API programmatically. The API allows you to create, manage, and monitor transaction spammers remotely.
Table of Contents
- API Overview
- Authentication
- Spammer Management
- Scenario Management
- Client Management
- Real-time Monitoring
- Import/Export
- System Endpoints
- Error Handling
- Example Workflows
- SDKs and Libraries
API Overview
The Spamoor daemon exposes a RESTful API on port 8080 (configurable) with the following characteristics:
- Base URL:
http://localhost:8080/api - Content Type:
application/json(for most endpoints) - Authentication: Optional bearer-token JWT verified against an
authenticatoorservice (see below). Disabled by default. - Documentation: Interactive Swagger UI at
/docs - Metrics: Prometheus metrics at
/metrics
Authentication
Authentication is opt-in. When the daemon is started without --auth-provider-url, the API is open and every request is accepted — suitable only for trusted networks.
When --auth-provider-url=<url> is set, all write endpoints (and SSE streams) require a JWT in the Authorization: Bearer <token> header. Tokens are verified against the JWKS published by the configured service-authenticatoor instance:
- Issuer (
iss): discovered from<url>/.well-known/openid-configuration(falls back to<url>itself). - Audience (
aud): the parent DNS zone of the auth service host (e.g.auth.foo.example.io→foo.example.io). - Identity for audit logs: the token's
emailclaim, falling back tosub.
For SSE endpoints that cannot set headers, the token may be passed as the auth query parameter (sent as Authorization: Bearer <token> server-side).
# Open mode (default): no header required
curl http://localhost:8080/api/spammers
# With auth-provider-url configured
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/spammers
Browser users are redirected to the authenticatoor's login page by the web UI; the token is then stored client-side and attached automatically.
Spammer Management
List All Spammers
Get a list of all configured spammers.
GET /api/spammers
Response:
[
{
"id": 1,
"name": "EOA Test Spammer",
"description": "Testing basic EOA transactions",
"scenario": "eoatx",
"status": 1,
"created_at": "2024-01-15T10:30:00.123456789Z"
}
]
Status Values:
0: Stopped1: Running2: Paused
Create a New Spammer
Create a new spammer with specified configuration.
POST /api/spammer
Content-Type: application/json
{
"name": "My EOA Spammer",
"description": "High-throughput EOA transactions",
"scenario": "eoatx",
"config": "throughput: 10\nmax_pending: 50\namount: 100\nrandom_amount: true",
"startImmediately": true
}
Response:
2
Returns the new spammer's ID.
Get Spammer Details
Get detailed information about a specific spammer.
GET /api/spammer/{id}
Response:
{
"id": 1,
"name": "EOA Test Spammer",
"description": "Testing basic EOA transactions",
"scenario": "eoatx",
"config": "throughput: 10\nmax_pending: 50\namount: 100",
"status": 1
}
Update Spammer Configuration
Update an existing spammer's configuration.
PUT /api/spammer/{id}
Content-Type: application/json
{
"name": "Updated EOA Spammer",
"description": "Modified configuration",
"config": "throughput: 15\nmax_pending: 100\namount: 200"
}
Start a Spammer
Start a paused or stopped spammer.
POST /api/spammer/{id}/start
Pause a Spammer
Pause a running spammer.
POST /api/spammer/{id}/pause
Delete a Spammer
Delete a spammer (stops it if running).
DELETE /api/spammer/{id}
Reclaim Funds
Reclaim funds from a spammer's wallet pool back to the root wallet.
POST /api/spammer/{id}/reclaim
Spammer Groups
A spammer group lets you control many spammers as one unit. A group applies a shared overlay of common config fields on top of each member's own full config, and can optionally split a global throughput (or total count) budget across members by weight.
A group is stored as a regular spammer row with the reserved sentinel scenario: "group".
Members keep their own full config; adding/removing a member only sets/clears its
group_id, so an ungrouped member behaves exactly like a standalone spammer.
List entries (GET /api/spammers) and GET /api/spammer/{id} include is_group,
group_id, and a parsed group_config (for groups) or member_config (for members).
Group rows report a derived status (running if any enabled member runs).
The lifecycle endpoints (/start, /pause, /reclaim) work on a group id and fan out
to its enabled members.
Create a Group
POST /api/spammer-group
Content-Type: application/json
{
"name": "mixed-load",
"description": "broad transaction variance",
"config": "base_fee: 20\ntip_fee: 2\n",
"throughput_mode": "shared",
"total_throughput": 100,
"total_count": 0,
"total_max_pending": 0,
"auto_restart_failed": false,
"auto_restart_cooldown": 0
}
throughput_mode is "shared" (split totals by weight) or "independent" (overlay
only). In shared mode total_throughput/total_count are split across enabled members;
each member's max_wallets is derived from its resolved throughput (≈ throughput/4,
clamped to [20, 1000]) and its max_pending defaults to 2× its resolved throughput.
Set total_max_pending to a non-zero value to instead split an explicit
concurrent-pending budget across members by weight. Returns the new group id.
With auto_restart_failed enabled, members that stop in the failed (error) state are
restarted automatically after auto_restart_cooldown seconds (0 = default 300).
Members stopped normally (paused or finished) are never restarted, and manual actions
taken during the cooldown (restart, pause, removal) always win.
Update a Group
PUT /api/spammer-group/{id}
Content-Type: application/json
{ "name": "...", "description": "...", "config": "...", "throughput_mode": "shared", "total_throughput": 200, "total_count": 0 }
Add / Update a Member
Assign a spammer to a group (or update its weight/enabled/order). A scenario with no
throughput/total_count field is rejected from a shared group.
PUT /api/spammer/{id}/group
Content-Type: application/json
{ "group_id": 100, "weight": 20, "enabled": true, "sort_order": 0 }
Detach a Member
Remove a spammer from its group, leaving it a working standalone spammer.
DELETE /api/spammer/{id}/group
Reorder Members
PUT /api/spammer-group/{id}/members/order
Content-Type: application/json
{ "order": [101, 103, 102] }
Delete a Group
For a group, the cascade query parameter controls member handling: cascade=false
(default) detaches members to standalone spammers; cascade=true deletes them too.
DELETE /api/spammer/{id}?cascade=false
Preview a Member's Effective Config
Returns the config a member will actually run with (group overlay + resolved
throughput/count split + derived max_wallets).
GET /api/spammer/{id}/effective-config
Scenario Management
List Available Scenarios
Get all available transaction scenarios.
GET /api/scenarios
Response:
[
{
"name": "eoatx",
"description": "Send standard EOA transactions with configurable amounts and targets"
},
{
"name": "blobs",
"description": "Send blob transactions with random data"
}
]
Get Scenario Configuration Template
Get a default YAML configuration template for a specific scenario.
GET /api/scenarios/{name}/config
Response:
# wallet settings
seed: eoatx-123456 # seed for the wallet
refill_amount: 5000000000000000000 # refill 5 ETH when
refill_balance: 1000000000000000000 # balance drops below 1 ETH
refill_interval: 600 # check every 10 minutes
# scenario: eoatx
throughput: 0
count: 0
max_pending: 0
# ... scenario-specific options
Client Management
List All Clients
Get information about all RPC clients.
GET /api/clients
Response:
[
{
"index": 0,
"name": "geth-1",
"group": "mainnet",
"groups": ["mainnet", "primary"],
"version": "Geth/v1.13.8-stable",
"block_height": 19234567,
"ready": true,
"rpc_host": "http://localhost:8545",
"enabled": true,
"name_override": "Custom Node Name"
}
]
Update Client Groups
Update the group assignment for a client.
PUT /api/client/{index}/group
Content-Type: application/json
{
"groups": ["mainnet", "backup", "testing"]
}
For backward compatibility, single group is also supported:
{
"group": "mainnet"
}
Enable/Disable Client
Control whether a client is used for transactions.
PUT /api/client/{index}/enabled
Content-Type: application/json
{
"enabled": false
}
Update Client Name
Set a custom display name for a client.
PUT /api/client/{index}/name
Content-Type: application/json
{
"name_override": "Primary Mainnet Node"
}
Real-time Monitoring
Get Spammer Logs
Retrieve recent log entries for a spammer.
GET /api/spammer/{id}/logs
Response:
[
{
"time": "2024-01-15T10:30:15.123456789Z",
"level": "info",
"message": "transaction submitted",
"fields": {
"hash": "0x1234567890abcdef...",
"nonce": "42",
"wallet": "0xabcdef..."
}
}
]
Stream Real-time Logs
Use Server-Sent Events (SSE) to stream logs in real-time.
GET /api/spammer/{id}/logs/stream?since=2024-01-15T10:30:00.000000000Z
Accept: text/event-stream
JavaScript Example:
const eventSource = new EventSource('/api/spammer/1/logs/stream');
eventSource.onmessage = function(event) {
const logs = JSON.parse(event.data);
logs.forEach(log => {
console.log(`[${log.level}] ${log.message}`, log.fields);
});
};
eventSource.onerror = function(event) {
console.error('SSE connection error:', event);
};
Bash/curl Example:
# Stream logs in real-time
curl -N "http://localhost:8080/api/spammer/1/logs/stream?since=2024-01-15T10:30:00.000000000Z" \
-H "Accept: text/event-stream" | while IFS= read -r line; do
if [[ $line == data:* ]]; then
# Extract JSON data after "data: " prefix
json_data="${line#data: }"
echo "$json_data" | jq -r '.[] | "[\(.level)] \(.message)"'
fi
done
Import/Export
Export Spammers
Export spammer configurations to YAML format.
POST /api/spammers/export
Content-Type: application/json
{
"spammer_ids": [1, 2, 3]
}
To export all spammers, send an empty array or omit the field:
{
"spammer_ids": []
}
Response:
- scenario: eoatx
name: "EOA Test Spammer"
description: "Testing basic EOA transactions"
config:
throughput: 10
max_pending: 50
amount: 100
Groups are exported as a scenario: group entry (its config is the sparse overlay and
group_config carries the mode/totals), and each member carries a group back-reference
plus its group_config (weight/enabled/sort_order). The parent group of any exported
member is always included so the import can re-link it:
- scenario: group
name: mixed-load
config:
base_fee: 20
group_config:
throughput_mode: shared
total_throughput: 100
total_count: 0
- scenario: eoatx
name: eoa-member
group: mixed-load
config:
amount: 100
group_config:
weight: 20
enabled: true
sort_order: 0
On import, groups are created first, then members are linked by group name. Importing the
same YAML through the CLI spamoor run path resolves groups in-process (overlay +
weight split) so members behave identically to the daemon.
Import Spammers
Import spammers from YAML data or URL.
POST /api/spammers/import
Content-Type: application/json
{
"input": "- scenario: eoatx\n name: Imported Spammer\n config:\n throughput: 5"
}
Or import from URL:
{
"input": "https://example.com/spammer-config.yaml"
}
Response:
{
"data": {
"imported": 2,
"skipped": 0,
"errors": []
}
}
System Endpoints
Prometheus Metrics
Get system metrics for monitoring.
GET /metrics
Response:
# HELP spamoor_spammer_running Number of running spammers
# TYPE spamoor_spammer_running gauge
spamoor_spammer_running 3
# HELP spamoor_transactions_sent_total Total number of transactions sent
# TYPE spamoor_transactions_sent_total counter
spamoor_transactions_sent_total{scenario="eoatx",spammer="1"} 1234
# HELP spamoor_transaction_failures_total Total number of failed transactions
# TYPE spamoor_transaction_failures_total counter
spamoor_transaction_failures_total{scenario="eoatx",spammer="1"} 5
API Documentation
Interactive Swagger UI documentation.
GET /docs/
Error Handling
The API uses standard HTTP status codes and returns JSON error responses:
Common Status Codes
200 OK: Request successful400 Bad Request: Invalid request data404 Not Found: Resource not found500 Internal Server Error: Server error
Error Response Format
{
"error": "Spammer not found"
}
Example Error Handling
# Function to handle API responses with error checking
call_api() {
local method="\$1"
local url="\$2"
local data="\$3"
if [[ -n "$data" ]]; then
response=$(curl -s -w "\n%{http_code}" -X "$method" \
-H "Content-Type: application/json" \
-d "$data" "$url")
else
response=$(curl -s -w "\n%{http_code}" -X "$method" "$url")
fi
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [[ $http_code -ge 400 ]]; then
error_msg=$(echo "$body" | jq -r '.error // "Unknown error"')
echo "Error: HTTP $http_code - $error_msg" >&2
return 1
fi
echo "$body"
}
# Usage example
spammer_config='{
"name": "Test Spammer",
"scenario": "eoatx",
"config": "throughput: 5",
"startImmediately": false
}'
if spammer_id=$(call_api "POST" "http://localhost:8080/api/spammer" "$spammer_config"); then
echo "Created spammer with ID: $spammer_id"
else
echo "Failed to create spammer"
fi
Example Workflows
Creating and Managing a Spammer
#!/bin/bash
BASE_URL="http://localhost:8080/api"
# Source the error handling function
source error_handling.sh # Contains the call_api function from above
# 1. List available scenarios
echo "=== Available Scenarios ==="
scenarios=$(call_api "GET" "$BASE_URL/scenarios")
echo "$scenarios" | jq -r '.[] | "- \(.name): \(.description)"'
echo
# 2. Get configuration template
echo "=== EOA Transaction Template ==="
default_config=$(curl -s "$BASE_URL/scenarios/eoatx/config")
echo "$default_config"
echo
# 3. Create a spammer
echo "=== Creating Spammer ==="
spammer_config='{
"name": "Test EOA Spammer",
"description": "API-created spammer for testing",
"scenario": "eoatx",
"config": "throughput: 5\nmax_pending: 20\namount: 100\nrandom_amount: true",
"startImmediately": false
}'
spammer_id=$(call_api "POST" "$BASE_URL/spammer" "$spammer_config")
spammer_id=$(echo "$spammer_id" | tr -d '"') # Remove quotes
echo "Created spammer with ID: $spammer_id"
# 4. Start the spammer
echo "=== Starting Spammer ==="
call_api "POST" "$BASE_URL/spammer/$spammer_id/start" >/dev/null
echo "Spammer started"
# 5. Monitor for 30 seconds
echo "=== Monitoring Logs for 30 seconds ==="
timeout 30s curl -N "$BASE_URL/spammer/$spammer_id/logs/stream" \
-H "Accept: text/event-stream" | while IFS= read -r line; do
if [[ $line == data:* ]]; then
json_data="${line#data: }"
echo "$json_data" | jq -r '.[] | "[\(.level)] \(.message)"' 2>/dev/null || true
fi
done
# 6. Pause the spammer
echo -e "\n=== Pausing Spammer ==="
call_api "POST" "$BASE_URL/spammer/$spammer_id/pause" >/dev/null
echo "Spammer paused"
# 7. Get final logs
echo "=== Final Log Summary ==="
recent_logs=$(call_api "GET" "$BASE_URL/spammer/$spammer_id/logs")
log_count=$(echo "$recent_logs" | jq 'length')
echo "Total log entries: $log_count"
# Show last 5 log entries
echo "Last 5 log entries:"
echo "$recent_logs" | jq -r '.[-5:] | .[] | "[\(.level)] \(.time) - \(.message)"'
Bulk Operations with Export/Import
#!/bin/bash
BASE_URL="http://localhost:8080/api"
# Export all current spammers
echo "=== Exporting Current Spammers ==="
curl -s -X POST "$BASE_URL/spammers/export" \
-H "Content-Type: application/json" \
-d '{}' > current_spammers.yaml
echo "Exported spammers to current_spammers.yaml"
cat current_spammers.yaml
# Modify configuration using yq (YAML processor)
echo -e "\n=== Modifying Configuration ==="
cp current_spammers.yaml modified_spammers.yaml
# Add "Modified" prefix to all spammer names
yq eval '.[] | .name = "Modified " + .name' -i modified_spammers.yaml
# Double throughput for all spammers that have it configured
yq eval '(.[] | select(.config.throughput) | .config.throughput) *= 2' -i modified_spammers.yaml
echo "Modified configuration:"
cat modified_spammers.yaml
# Import modified configuration
echo -e "\n=== Importing Modified Configuration ==="
modified_yaml=$(cat modified_spammers.yaml)
import_result=$(curl -s -X POST "$BASE_URL/spammers/import" \
-H "Content-Type: application/json" \
-d "{\"input\": $(echo "$modified_yaml" | jq -Rs .)}")
echo "Import result:"
echo "$import_result" | jq .
imported=$(echo "$import_result" | jq -r '.data.imported')
skipped=$(echo "$import_result" | jq -r '.data.skipped')
echo "Imported: $imported, Skipped: $skipped"
# Clean up temporary files
rm current_spammers.yaml modified_spammers.yaml
Client Management
#!/bin/bash
BASE_URL="http://localhost:8080/api"
# Get all clients
echo "=== Current Client Status ==="
clients=$(curl -s "$BASE_URL/clients")
echo "$clients" | jq -r '.[] | "Client \(.index): \(.name) (\(.rpc_host)) - \(if .ready then "Ready" else "Not Ready" end)"'
echo -e "\n=== Updating Client Configuration ==="
# Process each client
echo "$clients" | jq -c '.[]' | while read -r client; do
index=$(echo "$client" | jq -r '.index')
name=$(echo "$client" | jq -r '.name')
groups=$(echo "$client" | jq -r '.groups[]' 2>/dev/null)
name_override=$(echo "$client" | jq -r '.name_override // empty')
echo "Processing Client $index ($name)..."
# Update client groups for load balancing
if echo "$groups" | grep -q "mainnet"; then
current_groups=$(echo "$client" | jq -r '.groups')
new_groups=$(echo "$current_groups" | jq '. + ["load-balanced"] | unique')
curl -s -X PUT "$BASE_URL/client/$index/group" \
-H "Content-Type: application/json" \
-d "{\"groups\": $new_groups}" > /dev/null
echo " → Added 'load-balanced' group"
fi
# Set custom names for easier identification
if [[ -z "$name_override" ]]; then
custom_name="Node-$((index + 1))-$name"
curl -s -X PUT "$BASE_URL/client/$index/name" \
-H "Content-Type: application/json" \
-d "{\"name_override\": \"$custom_name\"}" > /dev/null
echo " → Set custom name: $custom_name"
fi
done
echo -e "\n=== Updated Client Status ==="
updated_clients=$(curl -s "$BASE_URL/clients")
echo "$updated_clients" | jq -r '.[] | "Client \(.index): \(.name_override // .name) (\(.rpc_host)) - Groups: \(.groups | join(", "))"'
SDKs and Libraries
While there are no official SDKs, the API is designed to be easily consumed by standard HTTP libraries:
Recommended Tools
- Bash/Shell:
curlfor HTTP requests,jqfor JSON processing,yqfor YAML processing - JavaScript/Node.js:
fetch,axiosfor HTTP;js-yamlfor configuration parsing - Go: Standard
net/httppackage orresty - Java:
OkHttp,Apache HttpClient - C#:
HttpClient
OpenAPI/Swagger Integration
The API includes OpenAPI documentation that can be used to generate client SDKs:
- Access the OpenAPI spec at
http://localhost:8080/docs/swagger.json - Use tools like
swagger-codegenoropenapi-generatorto generate clients - Example:
openapi-generator generate -i http://localhost:8080/docs/swagger.json -g python -o spamoor-python-client
Rate Limiting Considerations
While the API doesn't implement rate limiting, consider these best practices:
- Polling: Use reasonable intervals (1-5 seconds) when polling for status
- Streaming: Prefer SSE streaming for real-time updates over polling
- Batch Operations: Use export/import for bulk operations
- Connection Reuse: Use HTTP keep-alive and connection pooling
For high-frequency operations or production deployments, consider implementing client-side rate limiting and connection management.