HashAnchor Docs

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

Submit Hash
Batch (Merkle Tree)
Anchor On-Chain
Issue Receipt

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

EnvironmentURL
Productionhttps://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
  • lastUsedAt is 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

ScopeDescription
submitSubmit hashes (single and batch)
queryQuery stats, hash status, batches, exports, webhooks
adminFull access including tenant/key management and chain config
Keys with an empty scopes array are treated as unrestricted (full access). This supports legacy keys and admin operations.

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:

StatusMeaning
202Hash accepted and queued for anchoring
200Duplicate — this hash was already submitted by your tenant
400Invalid hash format or request body
401Missing or invalid API key
429Rate 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", ... }
  ]
}
Deduplication: If a hash has already been submitted by the same tenant, it is marked as 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.

ParamDefaultDescription
page1Page number
limit20Items 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": "..." }
  ]
}
The 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

POST /v1/device/register
curl -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

POST /v1/device/submit-signed
curl -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

GET /v1/device/list

Device History

GET /v1/device/:deviceId/history?limit=50&offset=0

Returns paginated submission history for a specific device.

Signature Algorithms: Supported algorithms are 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

StatusMeaning
pendingHash submitted, waiting to be batched
batchedIncluded in a Merkle tree, transaction pending
anchoredOn-chain — verified: true
failedAnchoring failed
not_foundHash 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
Receipts are immutable proofs. Store them alongside your original data for long-term verifiability, independent of the HashAnchor service.

Webhooks

Receive real-time notifications when your hashes are anchored. Webhook payloads are signed with HMAC-SHA256.

Endpoints

MethodPathDescription
GET/v1/webhooksList your webhooks
POST/v1/webhooksCreate a webhook
PATCH/v1/webhooks/:idUpdate a webhook
DELETE/v1/webhooks/:idDelete 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)
The 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:

AttemptDelay
1st retry1 second
2nd retry5 seconds
3rd retry15 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
}
FieldRequiredDescription
nameYesHuman-readable name (max 100 chars)
chainIdYesEVM chain ID (positive integer)
rpcUrlYesRPC endpoint URL
contractAddressYesHashAnchor contract address (42 chars)
privateKeyYesWallet private key (stored AES-encrypted)
confirmationsNoBlock confirmations required (default: 5)
isDefaultNoSet as default chain (default: false)

GET /admin/chains — List all chain configs (private key excluded from response).

Private keys are encrypted with AES using the CHAIN_KEY_ENCRYPTION_SECRET environment variable before storage.

Billing & Quotas

HashAnchor uses monthly hash submission limits based on your plan tier.

Plans

PlanMonthly Hash LimitDescription
Free100For testing and evaluation
BasicPlan-definedFor small-to-medium workloads
ProPlan-definedFor high-volume production use

Quota Headers

When a plan is assigned, every submission response includes quota headers:

HeaderDescription
X-Quota-LimitYour plan's monthly hash limit
X-Quota-UsedHashes submitted this calendar month
X-Quota-RemainingRemaining hashes for this month
Free-tier accounts (no plan assigned) have a hard limit of 100 hashes/month. Quota headers are not included for free-tier requests.

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:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute for this key
X-RateLimit-RemainingRemaining 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

MethodPathDescription
POST/v1/export/hashesExport hash submissions
POST/v1/export/receiptsExport 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 200 with { id, status, type, format, totalRows }
  • Completed: Streams the file download with appropriate Content-Type and Content-Disposition headers
  • 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

FieldDescription
tenantIdAuthenticated tenant ID
apiKeyIdAPI key used for the request
actionAction name (e.g. hash.submit)
resourceTypeResource category (hash, batch, etc.)
resourceIdSpecific resource identifier
ipAddressClient IP (X-Forwarded-For or X-Real-IP)
requestIdUnique request trace ID
statusCodeHTTP response status code

Action Naming Convention

RouteActionResource Type
POST /v1/hasheshash.submithash
POST /v1/hashes/batchhash.submit_batchhash
GET /v1/statsstats.viewstats
GET /v1/hashes/:hashhash.queryhash
GET /v1/batchesbatch.listbatch
POST /v1/webhookswebhook.createwebhook
DELETE /v1/webhooks/:idwebhook.deletewebhook
POST /v1/export/*export.createexport
POST /admin/tenantstenant.createtenant
POST /admin/api-keysapikey.createapikey
DELETE /admin/api-keys/:idapikey.revokeapikey

Query Audit Logs (Admin)

GET /admin/audit-logs

ParamDefaultDescription
page1Page number
limit50Items per page (max 100)
tenantIdFilter by tenant
actionFilter by action name
fromStart date (ISO 8601)
toEnd date (ISO 8601)

Admin API

Admin endpoints require the admin scope. They manage tenants, API keys, chains, and system operations.

Tenant Management

MethodPathDescription
POST/admin/tenantsCreate a tenant
GET/admin/tenantsList all tenants

Create tenant body:

{
  "name": "Acme Corp",                // 1-255 chars, required
  "contactEmail": "admin@acme.com"    // optional
}

API Key Management

MethodPathDescription
POST/admin/api-keysCreate an API key
DELETE/admin/api-keys/:idRevoke 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"
}
The 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

CodeMeaningCommon Causes
200OKSuccessful request (or duplicate hash)
201CreatedResource created (webhook, tenant, API key)
202AcceptedHash accepted for async processing
400Bad RequestInvalid hash format, missing required fields
401UnauthorizedMissing or invalid API key / JWT token
403ForbiddenInsufficient scope, tenant suspended
404Not FoundHash, batch, webhook, or export not found
409ConflictEmail already registered
410GoneExport file has expired
429Too Many RequestsRate limit or quota exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableDLQ 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.

MethodPathAuthScopeDescription
GET/healthNoHealth check
POST/auth/registerNoRegister new tenant
POST/auth/loginNoLogin, get JWT
POST/auth/provisionNoOne-step agent provisioning
GET/auth/meJWTCurrent user info
POST/v1/hashesAPI KeysubmitSubmit single hash
POST/v1/hashes/batchAPI KeysubmitSubmit batch (up to 100)
POST/v1/hashes/anchorAPI KeysubmitAnchor content (auto-hash)
POST/v1/hashes/statusAPI KeyqueryBulk hash status (up to 100)
GET/v1/hashes/:hashAPI KeyqueryHash status lookup
GET/v1/quotaAPI KeyqueryCheck plan quota & usage
GET/v1/statsAPI KeyqueryDashboard statistics
GET/v1/batchesAPI KeyqueryList batches (paginated)
GET/v1/batches/:idAPI KeyqueryBatch detail
POST/v1/device/registerAPI KeysubmitRegister IoT device
POST/v1/device/submit-signedAPI KeysubmitSubmit device-signed hash
GET/v1/device/listAPI KeysubmitList registered devices
GET/v1/device/:deviceId/historyAPI KeysubmitDevice submission history
GET/v1/verify/:hashNoPublic hash verification
GET/v1/receipts/:hashNoDownload receipt
GET/v1/public-keyNoEd25519 public key
GET/v1/webhooksAPI Keyadmin/queryList webhooks
POST/v1/webhooksAPI Keyadmin/queryCreate webhook
PATCH/v1/webhooks/:idAPI Keyadmin/queryUpdate webhook
DELETE/v1/webhooks/:idAPI Keyadmin/queryDelete webhook
POST/v1/export/hashesAPI KeyqueryExport hashes
POST/v1/export/receiptsAPI KeyqueryExport receipts
GET/v1/export/:idAPI KeyqueryExport status / download
POST/admin/tenantsAPI KeyadminCreate tenant
GET/admin/tenantsAPI KeyadminList tenants
POST/admin/api-keysAPI KeyadminCreate API key
DELETE/admin/api-keys/:idAPI KeyadminRevoke API key
GET/admin/audit-logsAPI KeyadminQuery audit logs
POST/admin/chainsAPI KeyadminAdd chain config
GET/admin/chainsAPI KeyadminList chain configs
GET/admin/dlq/statsAPI KeyadminDLQ statistics

For interactive API testing, use the Swagger UI or download the OpenAPI spec.