Overview
HashAnchor is a blockchain attestation service that anchors data hashes on-chain via Merkle trees. Submit any SHA-256 or Keccak-256 hash, and HashAnchor batches it into a Merkle tree, publishes the Merkle root to a blockchain smart contract, and issues a cryptographically signed receipt proving inclusion.
Pipeline
Key Features
- Hash Submission — Single or batch (up to 100) hash submission with deduplication
- Merkle Batching — Hashes are grouped into Merkle trees for gas-efficient on-chain anchoring
- On-Chain Verification — Public endpoint to verify any hash against the blockchain
- Signed Receipts — Ed25519-signed JSON receipts with Merkle proofs
- Webhooks — Real-time notifications with HMAC-SHA256 signatures
- Multi-Chain Support — Configurable blockchain targets (default: Polygon)
- Billing & Quotas — Tiered plans with monthly hash limits
- Batch Export — Async CSV/JSON export of hashes and receipts
- Audit Logging — Every API action is logged for compliance
Base URLs
| Environment | URL |
|---|---|
| Production | https://hashanchor.fengdeagents.site |
Getting Started
Follow these five steps to submit and verify your first hash.
Register an account
curl -X POST https://hashanchor.fengdeagents.site/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Acme Corp","email":"dev@acme.com","password":"securepass123"}'
Save the token from the response — it's a JWT valid for 7 days.
Login (if you already have an account)
curl -X POST https://hashanchor.fengdeagents.site/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"dev@acme.com","password":"securepass123"}'
Create an API key
Use the Admin API to create an API key with the desired scopes.
The key (prefixed ha_) is shown only once in the response.
curl -X POST https://hashanchor.fengdeagents.site/admin/api-keys \
-H "Authorization: Bearer <jwt_token>" \
-H "Content-Type: application/json" \
-d '{"tenantId":"<your-tenant-id>","name":"my-key","scopes":["submit","query"]}'
Submit a hash
curl -X POST https://hashanchor.fengdeagents.site/v1/hashes \
-H "Authorization: Bearer ha_<your-api-key>" \
-H "Content-Type: application/json" \
-d '{"hash":"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"}'
A 202 response means the hash was accepted and queued for anchoring.
Verify your hash
curl https://hashanchor.fengdeagents.site/v1/verify/0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
This public endpoint returns the verification status and on-chain proof.
Authentication
HashAnchor supports two authentication methods. Both use the Authorization: Bearer <token> header.
API Keys (recommended for programmatic access)
API keys have the format ha_<64 hex chars> (66 characters total). They are the primary authentication method for all /v1/* endpoints.
Authorization: Bearer ha_a1b2c3d4e5f6...
- Keys are hashed (SHA-256) before storage — the full key is shown only once at creation
- Each key has configurable scopes and a rate limit
lastUsedAtis updated on every successful authentication
JWT Tokens (for account management)
JWT tokens are issued by POST /auth/register and POST /auth/login. They expire after 7 days.
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
JWT tokens are primarily used for the /auth/me endpoint and admin operations.
Scopes
| Scope | Description |
|---|---|
submit | Submit hashes (single and batch) |
query | Query stats, hash status, batches, exports, webhooks |
admin | Full access including tenant/key management and chain config |
Hash Submission
All hashes must be 0x-prefixed, 64 hex character strings (32 bytes), matching the pattern /^0x[0-9a-fA-F]{64}$/.
POST /v1/hashes
Submit a single hash for anchoring.
Request body:
{
"hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"externalId": "invoice-2024-001" // optional, max 255 chars
}
Responses:
| Status | Meaning |
|---|---|
202 | Hash accepted and queued for anchoring |
200 | Duplicate — this hash was already submitted by your tenant |
400 | Invalid hash format or request body |
401 | Missing or invalid API key |
429 | Rate limit or monthly quota exceeded |
POST /v1/hashes/batch
Submit up to 100 hashes in a single request.
Request body:
{
"hashes": [
{ "hash": "0xabc...001", "externalId": "doc-1" },
{ "hash": "0xabc...002" },
{ "hash": "0xabc...003", "externalId": "doc-3" }
]
}
Response (202):
{
"accepted": 2,
"duplicates": 1,
"results": [
{ "hash": "0xabc...001", "status": "accepted", ... },
{ "hash": "0xabc...002", "status": "duplicate", ... },
{ "hash": "0xabc...003", "status": "accepted", ... }
]
}
duplicate and not counted against your quota.
Query Endpoints
All query endpoints are tenant-scoped — you only see your own data. Requires query or admin scope.
GET /v1/stats
Dashboard statistics for your tenant.
{
"totalHashes": 1250,
"pendingHashes": 12,
"anchoredHashes": 1230,
"totalBatches": 45,
"failedBatches": 0,
"monthlyHashes": 150
}
GET /v1/hashes/:hash
Look up the status of a specific hash. Returns 404 if not found within your tenant.
curl https://hashanchor.fengdeagents.site/v1/hashes/0xabcdef... \
-H "Authorization: Bearer ha_<key>"
GET /v1/batches
Paginated list of Merkle batches containing your hashes.
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 20 | Items per page (max 100) |
{
"data": [
{
"id": "uuid",
"merkleRoot": "0x...",
"leafCount": 50,
"status": "anchored",
"createdAt": "2024-01-15T10:30:00Z",
"txHash": "0x...",
"blockNumber": 12345678
}
],
"pagination": { "page": 1, "limit": 20, "total": 45 }
}
GET /v1/batches/:id
Detailed view of a specific batch, including all your hashes in that batch.
{
"id": "uuid",
"merkleRoot": "0x...",
"leafCount": 50,
"status": "anchored",
"txHash": "0x...",
"blockNumber": 12345678,
"blockTimestamp": 1705312200,
"gasUsed": "45000",
"hashes": [
{ "id": "uuid", "hash": "0x...", "status": "anchored", "externalId": "doc-1", "createdAt": "..." }
]
}
hashes array only includes your tenant's submissions, not other tenants' hashes in the same batch.
Device / IoT Endpoints
Register physical devices (IoT sensors, robots, edge gateways) and submit device-signed hashes. The platform verifies the signature using the device's registered public key before anchoring.
Register Device
/v1/device/registercurl -X POST /v1/device/register \
-H "Authorization: Bearer ha_xxx" \
-H "Content-Type: application/json" \
-d '{
"deviceId": "sensor-001",
"publicKey": "hex-encoded-ed25519-public-key",
"algorithm": "ed25519",
"metadata": { "location": "factory-A" }
}'
# Response: 201
# { "id": "uuid", "deviceId": "sensor-001", "algorithm": "ed25519", "status": "active" }
Submit Signed Hash
/v1/device/submit-signedcurl -X POST /v1/device/submit-signed \
-H "Authorization: Bearer ha_xxx" \
-H "Content-Type: application/json" \
-d '{
"deviceId": "sensor-001",
"hash": "0xabc...",
"signature": "hex-encoded-signature",
"metadata": { "sensorType": "temperature", "value": 23.5 }
}'
# Response: 202
# { "status": "accepted", "id": "123", "hash": "0xabc...", "signatureVerified": true }
List Devices
/v1/device/listDevice History
/v1/device/:deviceId/history?limit=50&offset=0Returns paginated submission history for a specific device.
ed25519 (default) and secp256k1.
The device signs the SHA-256 hash with its private key. HashAnchor verifies using the registered public key.
Public Verification
Verification is fully public — no authentication required. Anyone can verify a hash.
GET /v1/verify/:hash
curl https://hashanchor.fengdeagents.site/v1/verify/0xabcdef...
Anchored hash response (200):
{
"verified": true,
"status": "anchored",
"hash": "0xabcdef...",
"anchor": {
"txHash": "0x...",
"blockNumber": 12345678,
"blockTimestamp": 1705312200,
"chainId": 137,
"contractAddress": "0x...",
"onChainTimestamp": 1705312200
},
"receipt": { ... }
}
Pending hash response (200):
{
"verified": false,
"status": "pending",
"hash": "0xabcdef...",
"anchor": null,
"receipt": null
}
Unknown hash response (404):
{
"verified": false,
"status": "not_found",
"hash": "0xabcdef..."
}
Status Values
| Status | Meaning |
|---|---|
pending | Hash submitted, waiting to be batched |
batched | Included in a Merkle tree, transaction pending |
anchored | On-chain — verified: true |
failed | Anchoring failed |
not_found | Hash not in the system |
Receipts
Once a hash is anchored on-chain, a cryptographically signed receipt is generated containing a Merkle proof of inclusion.
GET /v1/receipts/:hash
Retrieve the anchor receipt for a specific hash. Returns 404 if the receipt has not been generated yet.
AnchorReceipt structure:
{
"@context": "https://hashanchor.fengdeagents.site/receipt/v1",
"hash": "0xabcdef...",
"merkleRoot": "0x...",
"proof": [
{ "position": "left", "data": "0x..." },
{ "position": "right", "data": "0x..." }
],
"anchor": {
"chainId": 137,
"contractAddress": "0x...",
"txHash": "0x...",
"blockNumber": 12345678,
"blockTimestamp": 1705312200
},
"signature": "base64-encoded-ed25519-signature"
}
Signature Verification
The signature field is an Ed25519 signature over the JSON-serialized { hash, merkleRoot, proof, anchor } object.
GET /v1/public-key
Retrieve the server's Ed25519 public key to verify receipt signatures.
curl https://hashanchor.fengdeagents.site/v1/public-key
Webhooks
Receive real-time notifications when your hashes are anchored. Webhook payloads are signed with HMAC-SHA256.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /v1/webhooks | List your webhooks |
| POST | /v1/webhooks | Create a webhook |
| PATCH | /v1/webhooks/:id | Update a webhook |
| DELETE | /v1/webhooks/:id | Delete a webhook |
Create Webhook
curl -X POST https://hashanchor.fengdeagents.site/v1/webhooks \
-H "Authorization: Bearer ha_<key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhook",
"events": ["hash.anchored"],
"secret": "your-secret-min-16-chars"
}'
url— HTTPS endpoint (required, max 2048 chars)events— Array of event types to subscribe to (default: all)secret— HMAC secret, 16–255 chars (auto-generated if omitted)
secret is returned only in the creation response. Store it securely — it cannot be retrieved again.
Update Webhook
curl -X PATCH https://hashanchor.fengdeagents.site/v1/webhooks/:id \
-H "Authorization: Bearer ha_<key>" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/new-endpoint", "status": "disabled"}'
HMAC-SHA256 Signature Verification
Each webhook delivery includes a signature header. Verify it to ensure the payload is authentic:
// Node.js example
const crypto = require('crypto');
function verifyWebhook(payload, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected)
);
}
Retry Logic
Failed deliveries are retried up to 3 times with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 5 seconds |
| 3rd retry | 15 seconds |
Multi-Chain
HashAnchor supports anchoring to multiple blockchains. Chain configurations are managed by admins.
Default Chain
The default anchoring target is Polygon (chain ID 137). When no specific chain is configured, all hashes are anchored on Polygon.
Chain Configuration (Admin)
POST /admin/chains
{
"name": "Polygon Mainnet",
"chainId": 137,
"rpcUrl": "https://polygon-rpc.com",
"contractAddress": "0x1234567890abcdef1234567890abcdef12345678",
"privateKey": "0x...",
"confirmations": 5,
"isDefault": true
}
| Field | Required | Description |
|---|---|---|
name | Yes | Human-readable name (max 100 chars) |
chainId | Yes | EVM chain ID (positive integer) |
rpcUrl | Yes | RPC endpoint URL |
contractAddress | Yes | HashAnchor contract address (42 chars) |
privateKey | Yes | Wallet private key (stored AES-encrypted) |
confirmations | No | Block confirmations required (default: 5) |
isDefault | No | Set as default chain (default: false) |
GET /admin/chains — List all chain configs (private key excluded from response).
CHAIN_KEY_ENCRYPTION_SECRET environment variable before storage.
Billing & Quotas
HashAnchor uses monthly hash submission limits based on your plan tier.
Plans
| Plan | Monthly Hash Limit | Description |
|---|---|---|
| Free | 100 | For testing and evaluation |
| Basic | Plan-defined | For small-to-medium workloads |
| Pro | Plan-defined | For high-volume production use |
Quota Headers
When a plan is assigned, every submission response includes quota headers:
| Header | Description |
|---|---|
X-Quota-Limit | Your plan's monthly hash limit |
X-Quota-Used | Hashes submitted this calendar month |
X-Quota-Remaining | Remaining hashes for this month |
Quota Exceeded
When you exceed your monthly limit, submission endpoints return 429:
{
"message": "Monthly hash limit reached (100). Upgrade your plan for higher limits."
}
Rate Limiting
Rate limits are enforced per API key using a Redis-backed 60-second fixed window.
Configuration
Each API key has a configurable rateLimitPerMinute (default: 60, max: 10,000). This is set at key creation time.
Response Headers
Every authenticated response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute for this key |
X-RateLimit-Remaining | Remaining requests in the current window |
Rate Limit Exceeded (429)
When the limit is exceeded, the response includes a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{
"message": "Rate limit exceeded"
}
The Retry-After value is the number of seconds until the current window resets.
Batch Export
Export your hashes or receipts as CSV or JSON files. Exports run asynchronously and are available for download once complete.
Start an Export
| Method | Path | Description |
|---|---|---|
| POST | /v1/export/hashes | Export hash submissions |
| POST | /v1/export/receipts | Export anchor receipts |
Request body:
{
"format": "csv", // "csv" (default) or "json"
"filters": {
"status": "anchored", // optional
"from": "2024-01-01", // optional
"to": "2024-12-31" // optional
}
}
Response (202):
{
"id": "export-uuid",
"status": "pending",
"type": "hashes",
"format": "csv"
}
Check Status / Download
GET /v1/export/:id
- In progress: Returns
200with{ id, status, type, format, totalRows } - Completed: Streams the file download with appropriate
Content-TypeandContent-Dispositionheaders - Expired: Returns
410 Gone— export files expire after 24 hours
curl -O https://hashanchor.fengdeagents.site/v1/export/<export-id> \
-H "Authorization: Bearer ha_<key>"
Audit Logging
Every authenticated API request is automatically logged for compliance and debugging.
Logged Fields
| Field | Description |
|---|---|
tenantId | Authenticated tenant ID |
apiKeyId | API key used for the request |
action | Action name (e.g. hash.submit) |
resourceType | Resource category (hash, batch, etc.) |
resourceId | Specific resource identifier |
ipAddress | Client IP (X-Forwarded-For or X-Real-IP) |
requestId | Unique request trace ID |
statusCode | HTTP response status code |
Action Naming Convention
| Route | Action | Resource Type |
|---|---|---|
POST /v1/hashes | hash.submit | hash |
POST /v1/hashes/batch | hash.submit_batch | hash |
GET /v1/stats | stats.view | stats |
GET /v1/hashes/:hash | hash.query | hash |
GET /v1/batches | batch.list | batch |
POST /v1/webhooks | webhook.create | webhook |
DELETE /v1/webhooks/:id | webhook.delete | webhook |
POST /v1/export/* | export.create | export |
POST /admin/tenants | tenant.create | tenant |
POST /admin/api-keys | apikey.create | apikey |
DELETE /admin/api-keys/:id | apikey.revoke | apikey |
Query Audit Logs (Admin)
GET /admin/audit-logs
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 50 | Items per page (max 100) |
tenantId | — | Filter by tenant |
action | — | Filter by action name |
from | — | Start date (ISO 8601) |
to | — | End date (ISO 8601) |
Admin API
Admin endpoints require the admin scope. They manage tenants, API keys, chains, and system operations.
Tenant Management
| Method | Path | Description |
|---|---|---|
| POST | /admin/tenants | Create a tenant |
| GET | /admin/tenants | List all tenants |
Create tenant body:
{
"name": "Acme Corp", // 1-255 chars, required
"contactEmail": "admin@acme.com" // optional
}
API Key Management
| Method | Path | Description |
|---|---|---|
| POST | /admin/api-keys | Create an API key |
| DELETE | /admin/api-keys/:id | Revoke an API key |
Create API key body:
{
"tenantId": "uuid", // required
"name": "production-key", // 1-255 chars, required
"scopes": ["submit", "query"], // default: [] (unrestricted)
"rateLimitPerMinute": 120 // 1-10000, default: 60
}
Response (201):
{
"id": "uuid",
"tenantId": "uuid",
"keyPrefix": "ha_a1b2c",
"name": "production-key",
"scopes": ["submit", "query"],
"rateLimitPerMinute": 120,
"createdAt": "2024-01-15T10:30:00Z",
"key": "ha_a1b2c3d4e5f6...full64hexchars"
}
key field is shown only once in the creation response. Store it securely.
Chain Configuration
See the Multi-Chain section for chain management endpoints.
Dead Letter Queue
GET /admin/dlq/stats
Returns DLQ statistics and recent jobs for monitoring failed background tasks.
{
"counts": { "completed": 0, "waiting": 2, "active": 0 },
"recentJobs": [
{ "id": "job-id", "data": { ... }, "timestamp": 1705312200 }
]
}
Returns 503 if the DLQ is not configured.
Error Handling
All errors follow a consistent JSON format:
{
"error": "Human-readable error message"
}
HTTP Status Codes
| Code | Meaning | Common Causes |
|---|---|---|
200 | OK | Successful request (or duplicate hash) |
201 | Created | Resource created (webhook, tenant, API key) |
202 | Accepted | Hash accepted for async processing |
400 | Bad Request | Invalid hash format, missing required fields |
401 | Unauthorized | Missing or invalid API key / JWT token |
403 | Forbidden | Insufficient scope, tenant suspended |
404 | Not Found | Hash, batch, webhook, or export not found |
409 | Conflict | Email already registered |
410 | Gone | Export file has expired |
429 | Too Many Requests | Rate limit or quota exceeded |
500 | Internal Server Error | Unexpected server error |
503 | Service Unavailable | DLQ not configured |
Scope Errors
HTTP/1.1 403 Forbidden
{
"error": "Insufficient scope. Required: submit or admin"
}
Validation Errors
HTTP/1.1 400 Bad Request
{
"error": "Invalid hash format"
}
API Reference
Complete list of all API endpoints.
| Method | Path | Auth | Scope | Description |
|---|---|---|---|---|
| GET | /health | No | — | Health check |
| POST | /auth/register | No | — | Register new tenant |
| POST | /auth/login | No | — | Login, get JWT |
| POST | /auth/provision | No | — | One-step agent provisioning |
| GET | /auth/me | JWT | — | Current user info |
| POST | /v1/hashes | API Key | submit | Submit single hash |
| POST | /v1/hashes/batch | API Key | submit | Submit batch (up to 100) |
| POST | /v1/hashes/anchor | API Key | submit | Anchor content (auto-hash) |
| POST | /v1/hashes/status | API Key | query | Bulk hash status (up to 100) |
| GET | /v1/hashes/:hash | API Key | query | Hash status lookup |
| GET | /v1/quota | API Key | query | Check plan quota & usage |
| GET | /v1/stats | API Key | query | Dashboard statistics |
| GET | /v1/batches | API Key | query | List batches (paginated) |
| GET | /v1/batches/:id | API Key | query | Batch detail |
| POST | /v1/device/register | API Key | submit | Register IoT device |
| POST | /v1/device/submit-signed | API Key | submit | Submit device-signed hash |
| GET | /v1/device/list | API Key | submit | List registered devices |
| GET | /v1/device/:deviceId/history | API Key | submit | Device submission history |
| GET | /v1/verify/:hash | No | — | Public hash verification |
| GET | /v1/receipts/:hash | No | — | Download receipt |
| GET | /v1/public-key | No | — | Ed25519 public key |
| GET | /v1/webhooks | API Key | admin/query | List webhooks |
| POST | /v1/webhooks | API Key | admin/query | Create webhook |
| PATCH | /v1/webhooks/:id | API Key | admin/query | Update webhook |
| DELETE | /v1/webhooks/:id | API Key | admin/query | Delete webhook |
| POST | /v1/export/hashes | API Key | query | Export hashes |
| POST | /v1/export/receipts | API Key | query | Export receipts |
| GET | /v1/export/:id | API Key | query | Export status / download |
| POST | /admin/tenants | API Key | admin | Create tenant |
| GET | /admin/tenants | API Key | admin | List tenants |
| POST | /admin/api-keys | API Key | admin | Create API key |
| DELETE | /admin/api-keys/:id | API Key | admin | Revoke API key |
| GET | /admin/audit-logs | API Key | admin | Query audit logs |
| POST | /admin/chains | API Key | admin | Add chain config |
| GET | /admin/chains | API Key | admin | List chain configs |
| GET | /admin/dlq/stats | API Key | admin | DLQ statistics |
For interactive API testing, use the Swagger UI or download the OpenAPI spec.