Base path: /api/v1. All authenticated routes require a session token issued via wallet-signature auth (see Auth module below). Responses are JSON. Errors follow the shape:
{ "error": { "code": "STRING_CODE", "message": "Human readable message" } }- Description: Issue a nonce for the client to sign, proving wallet ownership.
- Auth: None
- Request:
{ "walletAddress": "0x..." } - Response:
{ "nonce": "string" } - Errors:
400 INVALID_ADDRESS
- Description: Verify signed nonce, issue session token.
- Auth: None
- Request:
{ "walletAddress": "0x...", "signature": "0x..." } - Response:
{ "token": "jwt", "creator": { "id": "uuid", "walletAddress": "0x...", "isNewCreator": true } } - Errors:
401 INVALID_SIGNATURE,400 NONCE_EXPIRED
- Description: Fetch a public creator profile.
- Auth: None
- Response:
{ id, walletAddress, displayName, bio, avatarCid, organization, reputationScore } - Errors:
404 CREATOR_NOT_FOUND
- Description: Create a creator profile (post wallet auth).
- Auth: Required
- Request:
{ "displayName": "string", "bio": "string?", "avatarFile": "multipart" } - Validation:
displayName1–100 chars;avatarFile≤ 5MB, image types only - Response:
{ id, walletAddress, displayName, bio, avatarCid } - Errors:
400 VALIDATION_ERROR,409 PROFILE_ALREADY_EXISTS
- Description: Update own profile.
- Auth: Required, must match
:id - Request: partial of
POSTbody - Errors:
403 FORBIDDEN,400 VALIDATION_ERROR
- Description: Search/browse creators.
- Auth: None
- Request (query):
?q=string&organizationId=uuid&page=1&limit=20 - Response:
{ results: [...], page, total }
- Description: Submit asset file + draft metadata for AI enrichment and IPFS pinning, prior to on-chain registration. Does not touch the blockchain.
- Auth: Required
- Request:
{ "file": "multipart", "title": "string?", "description": "string?" } - Validation: File ≤ 50MB
- Response:
{ "contentHash": "0x...", "ipfsCid": "string", "suggestedMetadata": { "title", "description", "tags": [] } } - Errors:
400 VALIDATION_ERROR,502 IPFS_PIN_FAILED,502 AI_SERVICE_FAILED(degrades gracefully — returns withoutsuggestedMetadatarather than failing the whole request)
- Description: Pins the creator-approved final metadata (after reviewing/editing
/prepare's AI suggestions) to IPFS, returning the exact CID that must be used when signingregisterAsseton-chain. Must be called before the transaction is signed — the blockchain and PostgreSQL must reference the identical metadata CID, so this step cannot be skipped or reordered after confirmation. - Auth: Required
- Request:
{ "title": "string", "description": "string?", "tags": ["string"] } - Response:
{ "metadataCid": "string" } - Errors:
400 VALIDATION_ERROR,502 IPFS_PIN_FAILED
- Description: Called after on-chain registration succeeds. Verifies the transaction on-chain and persists the indexed record using the exact
ipfsCid/metadataCidthat were signed — never re-pins or recomputes metadata, since the blockchain is the source of truth for which CID was actually registered. - Auth: Required
- Request:
{ "contentHash": "0x...", "ipfsCid": "string", "metadataCid": "string", "txHash": "0x...", "finalMetadata": { "title", "description", "tags": [] } } - Response:
{ asset: {...} } - Errors:
400 TX_NOT_FOUND_ON_CHAIN,409 ASSET_ALREADY_REGISTERED
- Description: Returns the Proof of Origin Certificate for a registered asset (generated after
POST /assets/confirmsucceeds). - Auth: None — the certificate is a public proof artifact, same trust model as the verification page
- Response:
{ certificateUrl: "string", qrCodeUrl: "string" }— pre-rendered PDF/image URLs - Errors:
404 CERTIFICATE_NOT_FOUND(asset not yet registered or certificate generation failed — falls back gracefully, never blocks the core registration flow)
- Description: Fetch a single asset record.
- Auth: None
- Response:
{ id, creator, contentHash, ipfsCid, title, description, tags, registeredAt, txHash } - Errors:
404 ASSET_NOT_FOUND
- Description: Browse/search assets.
- Auth: None
- Request (query):
?q=string&creatorId=uuid&tag=string&page=1&limit=20 - Response:
{ results: [...], page, total }
- Description: Public verification lookup by hash — the core trust feature, must not require auth.
- Auth: None
- Request (query):
?hash=0x... - Response:
{ verified: true, asset: {...}, onChainTimestamp, creatorAddress }or{ verified: false } - Errors:
400 INVALID_HASH_FORMAT
- Description: Submit a review for an asset (after on-chain review tx confirmed).
- Auth: Required
- Request:
{ "assetId": "uuid", "rating": 1-5, "comment": "string?", "txHash": "0x..." } - Validation:
ratinginteger 1–5; one review per reviewer per asset - Errors:
409 ALREADY_REVIEWED,400 VALIDATION_ERROR,400 TX_NOT_FOUND_ON_CHAIN,400 TX_MISMATCH,404 CREATOR_NOT_FOUND,404 ASSET_NOT_FOUND
- Description: List reviews for an asset.
- Auth: None
- Request (query):
?page=1&limit=20 - Response:
{ results: [...], page, total, averageRating }
- Description: Fetch cached reputation score and components.
- Auth: None
- Response:
{ creatorId, score, assetCount, reviewCount, lastUpdated }
- Description: AI-assisted summary of a creator's own registered activity (assets, reviews, reputation), for their private dashboard.
- Auth: Required, must match
:id - Response:
{ totalAssets, totalReviews, averageRating, reputationScore, assetsOverTime: [{ date, count }], reviewsOverTime: [{ date, count, averageRating }], aiSummary } - Notes:
averageRatingisnull(not0) when there are no reviews.aiSummaryisnullif the AI call fails or is unavailable — the rest of the response is unaffected. - Errors:
401 UNAUTHORIZED,403 FORBIDDEN(not your profile),404 CREATOR_NOT_FOUND
- Description: Creator-facing dashboard data (own profile only).
- Auth: Required, must match
:id - Response:
{ views: [...], verifications: [...], topAssets: [...], reviewTrend: [...] } - Errors:
403 FORBIDDEN
- Description: Platform-wide analytics for admin dashboard.
- Auth: Required, admin role
- Response:
{ totalCreators, totalAssets, totalVerifications, dailyActive: [...] } - Errors:
403 FORBIDDEN
- Description: Unified search across creators, assets, and tags — powers the global search bar. Assets match on title or any of their associated tag names (case-insensitive). Each returned asset includes a
tagsarray of its actual stored tag names. - Auth: None
- Request (query):
?q=string&type=creators|assets|tags|all&page=1&limit=20 - Response:
{ creators: [...], assets: [...], tags: [...], page, total }—tagsis a real array ofTagrows ({ id, name }) matching the query;assets[].tagsis a real array of tag name strings for each matched asset. - Errors:
400 QUERY_TOO_SHORT(minimum 2 characters)
- Description: Basic liveness/readiness check for uptime monitoring and deploy verification.
- Auth: None
- Response:
{ status: "ok", timestamp, dependencies: { database: "ok", storage: "ok" } } - Notes: Does not check blockchain connectivity (that's
/contractsbelow) — keep this endpoint fast and cheap since it may be polled frequently by the hosting platform.
- Description: Debugging/ops endpoint exposing the current on-chain configuration and indexer sync state.
- Auth: None (read-only, non-sensitive — all values are public on-chain anyway)
- Response:
{
"network": "arbitrum-sepolia",
"contracts": {
"creatorRegistry": "0x...",
"assetRegistry": "0x...",
"reviewRegistry": "0x...",
"reputationManager": "0x..."
},
"latestIndexedBlock": 123456,
"latestChainBlock": 123458,
"syncStatus": "synced"
}- Notes:
syncStatusis"synced"whenlatestChainBlock - latestIndexedBlockis within a small tolerance, otherwise"lagging"— useful for quickly diagnosing "why isn't my registration showing up" during development and demos.
- All list endpoints are paginated (
page,limit, defaultlimit=20, max100). - All write endpoints that mirror an on-chain action (asset confirm, review submit) require the
txHashand validate it against the chain before persisting — the API never accepts an unconfirmed claim as fact. - Public endpoints (
verify,GETon creators/assets/reviews,search) are rate-limited per-IP to prevent scraping abuse without requiring auth. - All responses include standard security headers via Helmet; CORS is restricted to the deployed frontend origin(s), not wildcard.
- All requests are logged (method, path, status, latency) for debugging and demo-day troubleshooting; request bodies containing secrets or file contents are excluded from logs.
POST /assets/prepareenforces upload size limits and validates actual file MIME type (not just extension) before accepting a file for pinning.POST /auth/noncenonces expire after 5 minutes and are single-use — consumed and invalidated immediately upon successfulPOST /auth/verify, preventing replay.
- Current Persistence State: All pinned creative asset files and JSON metadata documents (
originchain.asset.v1,originchain.profile.v1) remain pinned indefinitely on Pinata once pinned viastorageService.pinFileorstorageService.pinJSON. No unpinning, garbage collection, or automated expiration logic exists anywhere in the codebase. - Accountable Account & Cost: Storage costs and IPFS pin maintenance are attributed entirely to the project's single shared Pinata service account (configured via
PINATA_JWT). - Honest Current Policy & Production Recommendation: Currently, there is no retention or unpinning policy implemented — all uploaded content persists indefinitely under the project's shared Pinata account. Before mainnet deployment, an explicit retention, storage quota, or unpinning policy for abandoned/unconfirmed draft assets must be implemented.