API Reference
Complete HTTP API specification for EWP v1.
Base Path
All endpoints are under /ewp/ prefix. Implementations MUST NOT use this prefix for non-protocol endpoints.
Common Conventions
JSON field naming: All JSON field names in request and response bodies use camelCase.
Error format: All error responses use a consistent JSON envelope:
{ "error": "ERROR_CODE" }
Timestamps: Timestamps appear in two forms: timestamp (Unix epoch seconds, integer) for protocol operations; createdAt / updatedAt (ISO 8601 UTC string) for human readability. The timestamp integer is always canonical.
Transport: All EWP communication MUST use HTTPS.
Read Endpoints (No Auth)
GET /ewp/profile
Returns the canonical identity and metadata for this node.
Response 200 OK:
{
"address": "0x1234567890abcdef1234567890abcdef12345678",
"url": "https://node.example.com",
"title": "My Node",
"description": "A personal publishing node",
"ewpVersion": "1",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-03-01T12:00:00.000Z"
}
When description is not set, its value MUST be null (not absent, not "").
| Status | Code |
|---|---|
| 500 | INTERNAL_ERROR |
GET /ewp/avatar
Returns the node's avatar as a binary image. This endpoint is OPTIONAL; nodes without an avatar MUST return 404 AVATAR_NOT_SET.
Supported formats: JPEG, PNG, WebP, GIF.
| Status | Code |
|---|---|
| 404 | AVATAR_NOT_SET |
| 500 | INTERNAL_ERROR |
GET /ewp/publications
Returns a paginated index of this node's signed publications.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
since | integer | — | Unix timestamp (seconds). Return only publications with timestamp strictly greater than this value. |
limit | integer | 100 | Results per page. Range: 1–1000. |
page | integer | 1 | Page number (1-indexed). |
Response 200 OK:
{
"data": [
{
"contentHash": "0xabc123def456...",
"publisherAddress": "0x1234567890abcdef1234567890abcdef12345678",
"signature": "0x3040...",
"timestamp": 1705312800,
"createdAt": "2026-01-15T10:30:00.000Z",
"contentKind": "POST",
"slug": "my-first-post"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 1,
"totalPages": 1,
"hasNextPage": false,
"hasPreviousPage": false
}
}
Notes:
- Results are ordered by
timestampascending. - Only signed publications are returned.
slugis a human-friendly alias and MAY benull.
| Status | Code |
|---|---|
| 400 | INVALID_LIMIT |
| 400 | INVALID_PAGE |
| 400 | INVALID_SINCE |
| 500 | INTERNAL_ERROR |
GET /ewp/contents/:contentHash
Returns the Content Unit identified by the given Content Hash.
Path Parameters:
| Parameter | Description |
|---|---|
contentHash | SHA-256 hash with 0x prefix (66 chars total) |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
timestamp | integer | Unix timestamp to scope the lookup |
thumb | string | Thumbnail size for images: sm, md, or lg |
Response Headers (Markdown):
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, immutable, max-age=31536000
Response Headers (Binary):
Content-Type: image/png
Content-Disposition: inline; filename="image.png"
Cache-Control: public, immutable, max-age=31536000
Accept-Ranges: bytes
| Status | Code |
|---|---|
| 400 | INVALID_HASH_FORMAT |
| 400 | INVALID_TIMESTAMP |
| 400 | INVALID_THUMBNAIL_SIZE |
| 404 | CONTENT_NOT_FOUND |
| 500 | INTERNAL_ERROR |
Write Endpoints (EIP-712 Auth)
POST /ewp/connections
Accepts a signed follow intent and creates a connection record.
Auth: EIP-712 signature (signer = follower)
Request Body:
{
"typedData": {
"domain": { "name": "epress world", "version": "1", "chainId": 1 },
"types": { ... },
"primaryType": "CreateConnection",
"message": {
"followerAddress": "0xfollower...",
"followeeAddress": "0xfollowee...",
"followeeUrl": "https://followee.example.com",
"followerUrl": "https://follower.example.com",
"timestamp": 1705312800
}
},
"signature": "0x..."
}
Response: 201 Created
{ "status": "created" }
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_PAYLOAD | Missing or malformed fields |
| 400 | INVALID_SIGNATURE | Signature verification failed |
| 400 | INVALID_TIMESTAMP | Timestamp outside ±1-hour window |
| 400 | INVALID_URL_FORMAT | URL fields are not valid HTTPS URLs |
| 401 | FOLLOWEE_IDENTITY_MISMATCH | Profile at followeeUrl doesn't match followeeAddress |
| 401 | FOLLOWER_IDENTITY_MISMATCH | Profile at followerUrl doesn't match followerAddress |
| 409 | CONNECTION_ALREADY_EXISTS | Connection already exists |
| 500 | INTERNAL_ERROR |
DELETE /ewp/connections
Accepts a signed connection termination request.
Auth: EIP-712 signature (follower for Unfollow; followee for RemoveFollower)
This endpoint uses HTTP DELETE with a JSON request body. EWP traffic is node-to-node HTTPS; implementations MUST NOT route through intermediaries that strip request bodies.
The receiver determines the operation from the recovered signer:
- Signer =
followerAddress→ Unfollow - Signer =
followeeAddress→ RemoveFollower
Request Body:
{
"typedData": {
"domain": { "name": "epress world", "version": "1", "chainId": 1 },
"types": { ... },
"primaryType": "DestroyConnection",
"message": {
"followerAddress": "0xfollower...",
"followeeAddress": "0xfollowee...",
"timestamp": 1705312800
}
},
"signature": "0x..."
}
Response: 204 No Content
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_PAYLOAD | Missing or malformed fields |
| 400 | INVALID_SIGNATURE | Signature verification failed, or signer matches neither address |
| 400 | INVALID_TIMESTAMP | Timestamp outside ±1-hour window |
| 404 | CONNECTION_NOT_FOUND | No matching connection record |
| 409 | STALE_REQUEST | Connection was re-established after this termination was signed |
| 500 | INTERNAL_ERROR |
POST /ewp/publications
Accepts an inbound Proof of Source from a followed publisher.
Auth: Active follow relationship + EIP-712 signature
Request Headers:
| Header | Required | Description |
|---|---|---|
X-Epress-Node-Updated | OPTIONAL | ISO 8601 UTC timestamp of publisher's current updatedAt |
Response: 202 Accepted
{ "status": "accepted" }
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_PAYLOAD | Missing or malformed fields |
| 400 | INVALID_SIGNATURE | Signature verification failed |
| 401 | NOT_FOLLOWING | publisherAddress not in receiver's follow list |
| 409 | REPLICATION_ALREADY_EXISTS | Publication already stored |
| 500 | INTERNAL_ERROR |
PATCH /ewp/nodes/:address
Applies a signed profile update from a remote node.
Auth: EIP-712 signature (signer = node owner)
Path Parameters:
| Parameter | Description |
|---|---|
address | The node owner's Ethereum address (EIP-55 checksummed) |
Request Body:
{
"typedData": {
"domain": { "name": "epress world", "version": "1", "chainId": 1 },
"types": { ... },
"primaryType": "NodeProfileUpdate",
"message": {
"ownerAddress": "0x1234...",
"url": "https://updated.example.com",
"title": "Updated Title",
"description": "New description",
"timestamp": 1705399200
}
},
"signature": "0x..."
}
Response: 204 No Content
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_PAYLOAD | Missing or malformed fields |
| 400 | ADDRESS_MISMATCH | Path :address does not match ownerAddress |
| 400 | INVALID_ADDRESS | Address is not a valid EIP-55 checksummed Ethereum address |
| 400 | INVALID_SIGNATURE | Signature verification failed |
| 400 | URL_VERIFICATION_FAILED | New URL's /ewp/profile did not return matching address |
| 500 | INTERNAL_ERROR |