This guide shows third-party developers how to fetch prompts, buy licenses, and verify ownership using the @prompthash/sdk package and the REST API directly.
npm install @prompthash/sdk @stellar/stellar-sdk
# or
yarn add @prompthash/sdk @stellar/stellar-sdkimport { PromptHashClient } from "@prompthash/sdk";
const client = new PromptHashClient({
apiUrl: "https://api.prompthash.io",
network: "mainnet", // or "testnet"
});// Sorted by upvotes (community governance rank)
const prompts = await client.listPrompts({ sort: "upvotes", limit: 20 });
console.log(prompts);
// [{ id, title, image, rating, upvotes, owner, priceUSDC }, ...]const prompt = await client.getPrompt("64abc123def456");
console.log(prompt.title, prompt.priceUSDC);PromptHash uses a two-step flow: the buyer submits a Stellar transaction on-chain, then records it with the backend to claim their license.
import {
Keypair,
Networks,
TransactionBuilder,
BASE_FEE,
Operation,
Asset,
} from "@stellar/stellar-sdk";
import { Server } from "@stellar/stellar-sdk/rpc";
const server = new Server("https://soroban-testnet.stellar.org");
const buyerKeypair = Keypair.fromSecret("S...");
// 1. Build and submit the on-chain purchase transaction
// (Exact call depends on the PromptHash contract ABI — see contracts/prompt_hash)
const txHash = await submitOnChainPurchase(promptId, buyerKeypair);
// 2. Record the purchase with the backend
const result = await client.recordPurchase(promptId, buyerKeypair.publicKey(), txHash);
if (result.success) {
console.log("License granted!");
}Before showing a buyer the decrypted prompt content, verify they hold a license:
const licensed = await client.hasLicense(promptId, buyerWallet);
if (!licensed) {
throw new Error("Unlicensed access");
}
// Proceed with unlock flowThe full license verification challenge-response flow:
1. Client calls GET /api/unlock/:promptId?wallet=G... → receives { nonce }
2. Client signs the nonce with Freighter wallet
3. Client calls POST /api/unlock/:promptId → { signature, wallet }
4. Server verifies signature against on-chain purchase record
5. Server returns the decryption key (valid for 60 seconds)
6. Client decrypts prompt content in-browser
// Cast an upvote (buyer wallets only)
const voteResult = await client.upvote(promptId, buyerWallet);
console.log("Total upvotes:", voteResult.upvotes);
// Get top-ranked prompts
const top = await client.getTopPrompts(10);
console.log(top); // [{ promptId, upvotes }, ...]| Method | Path | Description |
|---|---|---|
GET |
/api/prompts |
List prompts (?page=1&limit=20&sort=upvotes) |
GET |
/api/prompts/:id |
Get prompt by ID |
POST |
/api/prompts/:id/purchase |
Record a purchase |
GET |
/api/prompts/:id/license |
Check license (?wallet=G...) |
GET |
/api/unlock/:id |
Get challenge nonce |
POST |
/api/unlock/:id |
Submit signed challenge, receive key |
POST |
/api/governance/vote/:id |
Cast upvote |
DELETE |
/api/governance/vote/:id |
Remove upvote |
GET |
/api/governance/votes/:id |
Get vote count |
GET |
/api/governance/top |
Top-ranked prompts |
Full OpenAPI spec: docs/api-reference.md
All SDK methods throw typed errors. The REST API returns { error: string } with appropriate HTTP status codes:
400— Missing or invalid parameters403— Not authorised (e.g. non-buyer attempting to vote)404— Resource not found409— Conflict (e.g. duplicate vote)
Receive real-time notifications when marketplace events occur.
const response = await fetch("https://api.prompthash.io/api/webhooks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
walletAddress: "G...", // your creator wallet
url: "https://your-server.com/webhooks",
events: ["PromptPurchased", "PromptCreated"],
}),
});
const { secret } = await response.json();
// Store secret securely — it's only shown onceimport { createHmac } from "crypto";
function verifySignature(
secret: string,
rawBody: string,
signature: string,
): boolean {
const expected = `sha256=${createHmac("sha256", secret).update(rawBody).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;
}
// Express example
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-prompthash-signature"];
if (!verifySignature(SECRET, req.body.toString(), sig)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body);
const deliveryId = req.headers["x-prompthash-delivery"];
// Idempotency: skip if already processed
if (await wasProcessed(deliveryId)) {
return res.status(200).json({ received: true });
}
// Handle event...
await processEvent(event);
await markProcessed(deliveryId);
res.status(200).json({ received: true });
});PromptHash retries failed deliveries up to 3 times with exponential backoff (2s, 4s, 8s). Use the X-PromptHash-Delivery header for idempotency — each delivery attempt shares the same UUID.
import type { PromptInfo, PurchaseResult, ClientConfig } from "@prompthash/sdk";