This reference covers the marketplace and account endpoints used by the PromptHash frontend and the Express backend.
- Successful requests return JSON.
- Validation failures return
422with a field-level error map when available. - Missing resources return
404. - Auth or ownership failures return
403.
{
"error": "Invalid listing metadata",
"fields": {
"title": "Title is required.",
"price": "Price must be greater than zero."
}
}GET /api/prompts
Returns published, active marketplace prompts.
Optional query parameters:
categorywalletAddress
Example response:
[
{
"_id": "6650f1...",
"image": "https://example.com/cover.png",
"title": "Launch Strategy Pack",
"content": "Public preview text ...",
"owner": {
"username": "faithorji",
"walletAddress": "g..."
},
"price": 2.5,
"category": "Marketing",
"listingStatus": "published",
"isActive": true,
"salesCount": 12
}
]POST /api/prompts
Creates a creator listing after validating and normalizing the listing metadata.
Request body:
{
"image": "https://example.com/cover.png",
"title": "Launch Strategy Pack",
"content": "Long-form prompt content",
"walletAddress": "g...",
"price": 2.5,
"category": "marketing"
}Example response:
{
"message": "Prompt created successfully",
"prompt": {
"_id": "6650f1...",
"title": "Launch Strategy Pack",
"price": 2.5,
"category": "Marketing"
}
}POST /api/prompts/:id/publish
Publishes a draft prompt after validating required fields.
Example error response:
{
"error": "Prompt is not publishable",
"fields": {
"content": "Content is required."
}
}POST /api/prompts/:id/archive
Marks a prompt as archived and removes it from active workflow views.
GET /api/prompts/buyer/:walletAddress/owned
Returns prompts tied to purchases for the buyer wallet.
Example response:
{
"owned": [
{
"purchaseId": "66a1...",
"prompt": {
"_id": "6650f1...",
"title": "Launch Strategy Pack",
"content": "Public preview text ...",
"category": "Marketing"
},
"txHash": "tx_123",
"versionIndex": 1,
"purchasedAt": "2026-05-28T10:15:30.000Z"
}
]
}GET /api/prompts/buyer/:walletAddress/saved
Returns the buyer's saved marketplace listings.
Example response:
{
"saved": [
{
"purchaseId": "66a1...",
"prompt": {
"_id": "6650f1...",
"title": "Launch Strategy Pack",
"content": "Preview text ...",
"price": 2.5,
"category": "Marketing",
"owner": {
"username": "faithorji"
}
},
"savedAt": "2026-05-28T10:15:30.000Z"
}
]
}POST /api/prompts/buyer/save
Request body:
{
"walletAddress": "g...",
"promptId": "6650f1..."
}Example response:
{ "saved": true, "purchaseId": "66a1..." }POST /api/prompts/buyer/unsave
Request body:
{
"walletAddress": "g...",
"promptId": "6650f1..."
}Example response:
{ "saved": false }GET /api/prompts/creator/:walletAddress/drafts
Returns draft and ready-to-publish prompts for the connected creator wallet.
POST /api/prompts/version
Creates a new version for a prompt owned by the calling wallet.
POST /api/unlock/challenge
Issues a short-lived challenge token for wallet verification.
POST /api/unlock/verify
Verifies the wallet signature and on-chain entitlement before returning decrypted content.
- Listing metadata is normalized server-side before persistence.
- Category casing is canonicalized so the frontend can send user-friendly values.
- The buyer dashboard reads from
/api/prompts/buyer/:walletAddress/savedand/api/prompts/buyer/:walletAddress/ownedto populate separate library sections. - Save and unsave actions are intentionally idempotent from the UI perspective.
PromptHash delivers real-time event notifications via webhooks. Each subscription is tied to a creator wallet and receives signed JSON payloads when marketplace events occur.
| Event | Description |
|---|---|
PromptPurchased |
A buyer purchased a license for a prompt |
PromptCreated |
A new prompt was created and listed |
LicenseTransferred |
A license was transferred between wallets |
ReviewSubmitted |
A buyer submitted a review for a prompt |
POST /api/webhooks
Request body:
{
"walletAddress": "G...",
"url": "https://your-server.com/webhooks",
"events": ["PromptPurchased", "PromptCreated"]
}The events array is optional — defaults to ["PromptPurchased"]. Only events in the supported list are accepted; unknown events are silently filtered.
Example response (201):
{
"message": "Webhook registered.",
"id": "6650f1...",
"secret": "a1b2c3d4..."
}Important: The secret is returned only on creation. Store it securely — you need it to verify signatures.
GET /api/webhooks?walletAddress=G...
Returns the subscription (secret excluded from response).
DELETE /api/webhooks
Request body:
{
"walletAddress": "G..."
}{
"event": "PromptPurchased",
"deliveryId": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-07-20T12:00:00.000Z",
"data": {
"promptId": "42",
"buyer": "G...",
"title": "Launch Strategy Pack"
}
}| Header | Description |
|---|---|
X-PromptHash-Signature |
HMAC-SHA256 signature: sha256=<hex-digest> |
X-PromptHash-Delivery |
Unique delivery ID (UUID) for idempotency |
X-PromptHash-Event |
Event type name |
X-PromptHash-Timestamp |
ISO 8601 timestamp of event creation |
Verify the webhook payload using your stored secret:
import { createHmac } from "crypto";
function verifyWebhookSignature(
secret: string,
body: string,
signature: string,
): boolean {
const expected = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
if (expected.length !== signature.length) return false;
let result = 0;
for (let i = 0; i < expected.length; i++) {
result |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
}
return result === 0;
}Usage in an Express handler:
app.post("/webhooks", (req, res) => {
const signature = req.headers["x-prompthash-signature"];
const body = JSON.stringify(req.body);
if (!verifyWebhookSignature(YOUR_SECRET, body, signature)) {
return res.status(401).json({ error: "Invalid signature" });
}
const deliveryId = req.headers["x-prompthash-delivery"];
// Use deliveryId for idempotency — skip if already processed
// Process the event...
res.status(200).json({ received: true });
});Each delivery has a unique deliveryId (UUID) sent in the X-PromptHash-Delivery header. Your handler should:
- Check if the
deliveryIdhas been processed before - If yes, return 200 immediately (duplicate delivery)
- If no, process the event and record the
deliveryId
This is critical because PromptHash retries failed deliveries, which may result in duplicate HTTP requests.
- Failed deliveries (5xx errors, network timeouts, 429 rate limits) are retried up to 3 times
- Retries use exponential backoff: 2s, 4s, 8s
- 4xx errors (except 429) are treated as permanent failures — no retry
- After 10 consecutive failures, the subscription is automatically disabled
- All delivery attempts are logged in the
WebhookDeliveryLogcollection
Webhook endpoints should handle bursts gracefully. We recommend:
- Return 200 quickly and process asynchronously
- Use 429 with
Retry-Afterheader if your system is overloaded