Skip to content

Monitor NFT API Reference

API endpoints used by the Monitor app's NFT page (apps/monitor/src/app/nft/). All routes are Next.js API routes under /api/nft/ that either proxy to emprops-api or call on-chain contracts directly via viem.


1. Factory Deployment

GET /api/nft/deploy

Tab: Factory

Fetch saved factory deployment details for a chain.

ParamTypeDefaultDescription
chainIdnumber31337Blockchain network ID

Response:

json
{
  "success": true,
  "deployment": {
    "chainId": 31337,
    "deployer": "0x...",
    "timestamp": "2026-02-11T...",
    "contracts": {
      "DiamondCutFacet": "0x...",
      "DiamondLoupeFacet": "0x...",
      "OwnershipFacet": "0x...",
      "ERC721Facet": "0x...",
      "MintFacet": "0x...",
      "ContractURIFacet": "0x...",
      "CollectionInit": "0x...",
      "DiamondFactory": "0x..."
    }
  }
}

Checks bytecode of saved DiamondFactory address — returns { stale: true, error: "..." } if contract no longer exists (e.g. Hardhat restart).


POST /api/nft/deploy

Tab: Factory

Deploy the entire Diamond factory + all facets (7 contracts).

Request Body:

json
{ "chainId": 31337 }

Response: Same shape as GET — returns all deployed contract addresses.

On-chain: Deploys 6 facets + CollectionInit + DiamondFactory with all addresses as constructor args.


GET /api/nft/factory?chainId=X

Tab: Factory

Read factory contract state from chain.

ParamTypeDefaultDescription
chainIdnumber31337Blockchain network
factoryAddressstringfrom deploymentOverride address

Response:

json
{
  "success": true,
  "factory": {
    "address": "0x...",
    "collectionCount": "5",
    "owner": "0x...",
    "facets": {
      "diamondCutFacet": "0x...",
      "diamondLoupeFacet": "0x...",
      "ownershipFacet": "0x...",
      "erc721Facet": "0x...",
      "mintFacet": "0x...",
      "collectionInit": "0x..."
    }
  }
}

Returns { stale: true } if bytecode is empty.


2. AI Collection Generation

POST /api/nft/collections/generate

Tab: Collections

Generate a collection config from a concept using Gemini AI. Optionally creates the collection in one call.

Proxies to: POST emprops-api/nft/collections/generate

Request Body:

json
{
  "concept": "Cyberpunk cats in a neon city",
  "save": true,
  "blockchain": "ARBITRUM",
  "project_id": "uuid (optional)",
  "social_org": "farcaster (optional)",
  "social_identifier": "12345 (optional)"
}
FieldTypeRequiredDescription
conceptstringYesText description for Gemini to generate a collection from (min 3 chars)
savebooleanNoWhen true, also creates the collection in the database (default: false)
blockchainstringNo"ETHEREUM", "BASE", "TEZOS", "ARBITRUM" (default: "ARBITRUM")
project_idUUIDNoProject to create the collection in (only used when save: true)
social_orgstringNoCustodial social platform
social_identifierstringNoCustodial social user ID

Response (save: false):

json
{
  "success": true,
  "generated": {
    "title": "Cyberpunk Cats",
    "description": "Neon-drenched feline portraits...",
    "prompt": "A $cat_style cyberpunk cat in a neon city...",
    "variables": [...],
    "data": { "version": "v2", "steps": [...], "variables": [...] }
  }
}

Response (save: true):

json
{
  "success": true,
  "generated": { ... },
  "collection_id": "uuid"
}

3. Collection CRUD (Database)

POST /api/nft/collections/create

Tab: Collections

Create a new NFT collection in the EmProps DB (no on-chain action). When using the generate endpoint with save: true, this step is handled automatically.

Proxies to: POST emprops-api/nft/collections

Request Body:

json
{
  "title": "My Collection",
  "description": "A description",
  "editions": 1000,
  "price": 0.5,
  "blockchain": "ARBITRUM",
  "data": {
    "version": "v2",
    "steps": [
      {
        "id": 1,
        "nodeName": "emerge-base-pro",
        "nodePayload": {
          "intent": "A generation prompt with $variables",
          "user_image": null,
          "optional_ref_image": null,
          "seed": null
        }
      }
    ],
    "generations": { "hashes": [], "generations": 1, "use_custom_hashes": false },
    "variables": [
      {
        "name": "color",
        "type": "pick",
        "value_type": "strings",
        "is_feature": true,
        "feature_name": "Color",
        "is_selectable": true,
        "is_user_customizable": false,
        "required": false,
        "value": [
          { "value": "red", "display_name": "Red", "weight": 1 },
          { "value": "blue", "display_name": "Blue", "weight": 1 }
        ]
      }
    ]
  }
}

Response:

json
{
  "success": true,
  "collection": { "id": "uuid", "title": "My Collection", ... },
  "contractParams": {
    "name": "MyCollection",
    "symbol": "MC",
    "baseURI": "https://metadata.emprops.app/collections/uuid/tokens/",
    "contractURI": "https://metadata.emprops.app/collections/uuid/metadata.json",
    "maxSupply": 1000
  }
}

Auto-generates contractParams used to pre-fill the on-chain deploy form.


GET /api/nft/collections/lookup?collectionId=<uuid>

Tab: Collections

Fetch collection metadata + deployment status from EmProps DB.

Proxies to: GET emprops-api/nft/collections/:collectionId

Response:

json
{
  "success": true,
  "collection": {
    "id": "uuid",
    "title": "My Collection",
    "contract_address": "0x...",
    "chain_id": 31337,
    "data": { "variables": [...], "steps": [...] },
    ...
  },
  "contractParams": { "name": "...", "symbol": "...", ... }
}

PUT /api/nft/collections/update

Tab: Collections

Update an existing collection in the DB.

Proxies to: PUT emprops-api/nft/collections/:collectionId

Request Body:

json
{
  "collectionId": "uuid",
  "title": "Updated Title",
  "description": "Updated description",
  "editions": 2000,
  "data": { "version": "v2", "steps": [...], "variables": [...] }
}

POST /api/nft/collections/reset

Tab: Collections

Clear on-chain deployment fields when contract is stale (e.g. Hardhat restart).

Proxies to: POST emprops-api/nft/collections/:collectionId/reset

Request Body:

json
{ "collectionId": "uuid" }

Clears contract_address, chain_id, deploy_tx_hash from DB so the collection can be re-deployed.


4. Collection On-Chain Operations

GET /api/nft/collections?chainId=X&addresses=0x...,0x...

Tab: Collections

Read collection metadata directly from deployed Diamond contracts on-chain.

ParamTypeDescription
chainIdnumberBlockchain network (default 31337)
addressesstringComma-separated contract addresses

Response:

json
{
  "success": true,
  "collections": [
    {
      "address": "0x...",
      "name": "My Collection",
      "symbol": "MC",
      "owner": "0x...",
      "minter": "0x...",
      "totalSupply": "42",
      "maxSupply": "1000",
      "mintActive": true,
      "contractURI": "https://...",
      "collectionId": "uuid"
    }
  ],
  "invalidAddresses": ["0x..."]
}

On-chain reads: name(), symbol(), totalSupply(), maxSupply(), mintActive, minter, owner(), contractURI(). Extracts collection UUID from contractURI.


POST /api/nft/collections

Tab: Collections (Deploy Contract button)

Deploy a new Diamond collection contract via factory.

Request Body:

json
{
  "chainId": 31337,
  "name": "My Collection",
  "symbol": "MC",
  "baseURI": "https://metadata.emprops.app/collections/uuid/tokens/",
  "contractURI": "https://metadata.emprops.app/collections/uuid/metadata.json",
  "maxSupply": "1000",
  "empropsCollectionId": "uuid"
}

Response:

json
{
  "success": true,
  "collection": {
    "address": "0x...",
    "name": "My Collection",
    "symbol": "MC",
    "owner": "0x...",
    "totalSupply": "0",
    "maxSupply": "1000",
    "mintActive": true
  },
  "txHash": "0x...",
  "salt": "0x..."
}

On-chain flow:

  1. collectionCount() → determine salt
  2. predictAddress() → predict deployment address
  3. createCollection(struct) → deploy Diamond proxy
  4. If empropsCollectionId provided → saves contract_address back to DB via POST emprops-api/nft/collections/{id}/deploy-status

4. Minting

POST /api/nft/mint

Tab: Mint

Mint a token and trigger async generation.

Proxies to: POST emprops-api/nft/collections/:collectionId/mint-and-generate

Request Body:

json
{
  "collectionId": "uuid",
  "recipient": "0x...",
  "variables": {
    "color": "red",
    "style": "abstract"
  }
}

Response:

json
{
  "success": true,
  "mint": {
    "tokenId": 42,
    "txHash": "0x...",
    "recipient": "0x...",
    "deckId": "uuid",
    "metadataUri": "https://..."
  },
  "generation": {
    "jobId": "uuid",
    "outputId": "uuid",
    "deckId": "uuid",
    "status": "generating"
  }
}

emprops-api flow:

  1. Mints token on-chain immediately → reserves tokenId
  2. Creates deck with mint state via mint_state on generations endpoint
  3. Triggers async image generation (same pipeline as POST /collections/:id/generations)
  4. Client polls GET /nft/decks/:deckId/mint-status for completion

GET /api/nft/mint?collectionId=<uuid>

Tab: Mint (mint history list)

List all mints for a collection.

Proxies to: GET emprops-api/nft/collections/:collectionId/mints

Response:

json
{
  "success": true,
  "mints": [
    {
      "id": "deck-uuid",
      "token_id": 42,
      "mint_tx_hash": "0x...",
      "mint_recipient": "0x...",
      "mint_status": "minted",
      "minted_at": "2026-02-11T..."
    }
  ]
}

5. Metadata Inspection

GET /api/nft/metadata

Tabs: Metadata, Mint, Collections

Fetch and parse metadata from on-chain contracts. Server-side fetch handles localhost URIs that browsers can't reach.

ParamTypeDescription
chainIdnumberBlockchain (default 31337)
addressstringContract address
typestringcontract, token, or tokens
tokenIdnumberRequired for type=token

type=contract — ERC-7572 collection metadata:

json
{
  "success": true,
  "uri": "https://metadata.emprops.app/collections/uuid/metadata.json",
  "metadata": {
    "name": "My Collection",
    "description": "...",
    "image": "https://...",
    "external_link": "https://..."
  }
}

type=token — ERC-721 token metadata:

json
{
  "success": true,
  "uri": "https://metadata.emprops.app/collections/uuid/tokens/42.json",
  "owner": "0x...",
  "metadata": {
    "name": "Token #42",
    "description": "...",
    "image": "https://...",
    "attributes": [
      { "trait_type": "Color", "value": "Red" }
    ]
  }
}

type=tokens — Enumerate all tokens (capped at 100):

json
{
  "success": true,
  "tokens": [
    { "tokenId": 1, "uri": "https://...", "owner": "0x..." },
    { "tokenId": 2, "uri": "https://...", "owner": "0x..." }
  ],
  "totalSupply": 42
}

6. Admin Actions

POST /api/nft/admin

Tab: Admin

Execute on-chain admin functions on a Diamond collection.

Request Body:

json
{
  "chainId": 31337,
  "collectionAddress": "0x...",
  "action": "setMintActive",
  "value": true
}
ActionValue TypeContract Function
setMintActivebooleanMintFacet.setMintActive(bool)
setMinteraddressMintFacet.setMinter(address)
setMaxSupplynumber stringMintFacet.setMaxSupply(uint256)
setBaseURIURL stringERC721Facet.setBaseURI(string)
setContractURIURL stringContractURIFacet.setContractURI(string)
transferOwnershipaddressOwnershipFacet.transferOwnership(address)

Response:

json
{ "success": true, "txHash": "0x...", "action": "setMintActive", "value": true }

7. Ownership (Ponder Indexer Data)

GET /api/nft/ownership?collectionId=<uuid>

Tab: Ownership

Fetch indexed token ownership data for a collection. Data is populated by the Ponder blockchain indexer.

Proxies to: GET emprops-api/nft/collections/:collectionId/ownership

Response:

json
{
  "success": true,
  "tokens": [
    {
      "id": "deck-uuid",
      "token_id": 1,
      "current_owner": "0x...",
      "mint_recipient": "0x...",
      "mint_status": "minted",
      "last_transfer_at": "2026-02-25T..."
    }
  ]
}
FieldSourceDescription
current_ownerPonder indexerCurrent on-chain owner (updated on each Transfer event)
mint_recipientMint APIOriginal mint recipient (set at mint time)
last_transfer_atPonder indexerTimestamp of most recent transfer

GET /api/nft/ownership/transfers?deckId=<uuid>

Tab: Ownership (expanded token row)

Fetch transfer history for a specific token/deck.

Proxies to: GET emprops-api/nft/decks/:deckId/transfers

Response:

json
{
  "success": true,
  "transfers": [
    {
      "id": "uuid",
      "deck_id": "uuid",
      "from_address": "0x0000000000000000000000000000000000000000",
      "to_address": "0x...",
      "block_number": "123456789",
      "tx_hash": "0x...",
      "log_index": 0,
      "timestamp": "2026-02-25T..."
    }
  ]
}

Mint events have from_address = 0x0000...0000. Transfers are ordered by block_number descending.


GET /api/nft/ownership/indexer-status

Tab: Ownership (status banner)

Fetch Ponder indexer operational state.

Proxies to: GET emprops-api/nft/indexer/status

Response:

json
{
  "success": true,
  "state": {
    "id": "singleton",
    "status": "idle",
    "last_indexed_block": "123456789",
    "chain_id": 42161,
    "started_at": "2026-02-25T...",
    "updated_at": "2026-02-25T..."
  }
}
StatusMeaning
idleIndexer is caught up to chain head
indexingProcessing new events
reindexingFull reindex in progress — ownership data temporarily incomplete

Shared Helpers

apps/monitor/src/lib/nft/api.ts:

  • getApiUrl() — returns EMPROPS_API_URL env var
  • apiHeaders(contentType?) — returns { "Content-Type": "application/json", "Authorization": "Bearer ..." }

Generation API (emprops-api direct)

The mint endpoint ultimately calls the same generation handler as:

POST /collections/:id/generations

Schema:

json
{
  "variables": { "color": "red", "farcaster_pfp": "https://..." },
  "workflow_id": "optional",
  "workflow_priority": 100,
  "job_type": "farcaster",
  "social_org": "farcaster",
  "social_identifier": "12345",
  "deck_id": "optional-uuid",
  "mint_state": {
    "token_id": 42,
    "tx_hash": "0x...",
    "recipient": "0x...",
    "metadata_uri": "https://..."
  }
}
  • variables — key/value overrides for collection variables + component fields (e.g. seed)
  • deck_id — use existing deck instead of creating one
  • mint_state — (optional) when present, creates the deck with on-chain mint data baked in. Used by mint-and-generate.

Response:

json
{
  "data": { "jobId": "uuid", "outputId": "uuid", "deckId": "uuid" },
  "error": null
}

8. Image Upload

POST /api/nft/upload-image

Tab: Collections (Reference Images, Variable Image Upload)

Upload an image and create a flat_file record for asset tracking.

Proxies to: POST emprops-api/upload-base64 with create_flat_file: true

Request Body:

json
{
  "base64Data": "iVBOR...",
  "mimeType": "image/png",
  "filename": "ref-scene.png",
  "userId": "canonical-user-uuid"
}

Response:

json
{
  "success": true,
  "url": "user-uploads/user-id/abc-123/def456.png",
  "path": "user-uploads/user-id/abc-123/def456.png",
  "flatFileId": 12345
}

The returned url is a storage path that SmartImage resolves through the CDN (imgproxy). Store this path in variable values — the ComfyUI workflow's EmProps_Image_Loader will download from the CDN at runtime.


9. Samples

POST /api/nft/samples

Tab: Collections (Samples section)

Generate sample images for a collection. Supports free tier + paid (USDC) samples.

Proxies to: POST emprops-api/nft/collections/:collectionId/samples

Request Body:

json
{
  "collectionId": "uuid",
  "count": 3,
  "wallet_address": "0x...",
  "chain_id": 421614,
  "variables": {
    "farcaster_pfp": "https://imagedelivery.net/.../original"
  },
  "tx_hash": "0x... (required if paid samples)"
}
FieldTypeRequiredDescription
collectionIdUUIDYesCollection to generate samples for
countnumberYesNumber of samples (1-10)
wallet_addressstringYesWallet address of requester
chain_idnumberNoBlockchain network ID
variablesobjectNoVariable overrides — customizable variables accept any value, selectable variables pick from presets. Omitted variables are picked randomly from presets.
tx_hashstringNoUSDC payment transaction hash (required when free samples exhausted)

Variable Override Behavior:

  • farcaster_pfp — auto-populated with creator's social PFP in the monitor UI. At mint time, the minter's PFP is used instead.
  • Customizable variables — any value can be passed (free-form input)
  • Selectable variables — value must be one of the preset options (dropdown in UI)
  • Omitted or empty — random pick from preset values

Response:

json
{
  "success": true,
  "samples": [
    { "outputId": "uuid", "jobId": "uuid", "status": "pending", "is_free": true }
  ]
}

Payment Flow:

  1. Check free_remaining via sample-status endpoint
  2. If paid samples needed, transfer USDC to compute recipient on-chain
  3. Include tx_hash in request — API verifies payment before generating

GET /api/nft/samples?collectionId=<uuid>

Tab: Collections (Samples section)

List all samples for a collection.

Proxies to: GET emprops-api/nft/collections/:collectionId/samples

Response:

json
{
  "success": true,
  "samples": [
    {
      "outputId": "uuid",
      "url": "https://cdn.emprops.ai/...",
      "status": "completed",
      "is_free": true,
      "created_at": "2026-03-17T...",
      "jobId": "uuid",
      "traits": [
        { "trait_type": "Color", "value": "Red" },
        { "trait_type": "Style", "value": "Abstract" }
      ]
    }
  ]
}

The traits array is extracted from resolved variable values after generation completes — only variables with is_feature: true appear as traits.


GET /api/nft/sample-status?collectionId=<uuid>

Tab: Collections (Samples section)

Get free/paid sample counts and pricing for a collection.

Response:

json
{
  "success": true,
  "free_remaining": 2,
  "free_limit_per_collection": 3,
  "free_used_collection": 1,
  "free_limit_per_user": 10,
  "free_used_user": 3,
  "paid_count": 5,
  "compute_cost_per_sample": 0.34
}

Variable Schema

Variables define the randomizable/customizable parameters of a collection. Each variable has flags controlling how it behaves at generation and mint time:

json
{
  "name": "color",
  "type": "pick",
  "value_type": "strings",
  "is_feature": true,
  "feature_name": "Color",
  "is_selectable": false,
  "is_user_customizable": false,
  "required": false,
  "value": [
    { "value": "red", "display_name": "Red", "weight": 1 },
    { "value": "blue", "display_name": "Blue", "weight": 1 }
  ]
}
FlagEffect
is_featureVariable becomes an NFT trait attribute (shown in metadata)
is_selectableMinter picks from preset value options via dropdown at mint time
is_user_customizableMinter provides any value via free-form input at mint time
required(Only when customizable) Minter must provide a value — cannot be left empty

Behavior when no override is provided: Value is randomly picked from the preset value array, weighted by weight.

Special variables:

  • farcaster_pfp — always present, is_user_customizable: true. Defaults to blank placeholder. At sample time, auto-populated with creator's PFP. At mint time, auto-populated with minter's Farcaster PFP.
  • ref_image_1 through ref_image_5 — creator reference images, defaults to blank placeholder. Blank images are skipped by the Gemini node.

UI Tab → API Mapping

TabEndpoints Used
FactoryGET /api/nft/deploy, POST /api/nft/deploy, GET /api/nft/factory
CollectionsPOST /api/nft/collections/create, GET /api/nft/collections/lookup, PUT /api/nft/collections/update, POST /api/nft/collections/reset, GET /api/nft/collections, POST /api/nft/collections, POST /api/nft/upload-image, POST /api/nft/samples, GET /api/nft/samples, GET /api/nft/sample-status
MintGET /api/nft/collections/lookup, POST /api/nft/mint, GET /api/nft/mint, GET /api/nft/metadata
MetadataGET /api/nft/metadata (contract, token, tokens)
AdminPOST /api/nft/admin
OwnershipGET /api/nft/ownership, GET /api/nft/ownership/transfers, GET /api/nft/ownership/indexer-status

Released under the MIT License.