Medialane
FeaturesPricingDocsConnect
Medialane

Permissionless IP infrastructure on Starknet. One REST API for orders, metadata, collections, and activities.

Platform

  • Features
  • Pricing
  • Docs
  • API Reference
  • SDK

Community

  • Connect
  • Changelog
  • Workshop
  • DAO ↗

Legal

  • Terms
  • Privacy

© 2026 Medialane. All rights reserved. · Powered by Starknet

    Documentation

    • Quick Start
    • Authentication
    • Rate Limits
    • Error Codes
    • Orders
    • Collections
    • Minting
    • Tokens
    • Batch Tokens
    • Activities
    • Intents
    • Checkout Intent
    • Metadata
    • Search
    • Events
    • Comments
    • Counter-offers
    • Remix Licensing
    • Claims
    • Profiles
    • Portal
    • Webhooks
    • Health
    • Technical
    • Install
    • Configure
    • Minting
    • Marketplace
    • API Client
    • Comments
    • Counter-offers
    • Remix Licensing
    • Error Codes
    API Reference

    API Reference

    Full endpoint reference for the Medialane REST API. Base URL: https://medialane-backend-production.up.railway.app

    Health

    Public uptime and system status. Use this to monitor indexer lag and database connectivity.

    GET/health

    Get system health status, including database connectivity and indexer lag.

    cURL

    curl "https://medialane-backend-production.up.railway.app/health"

    Response

    {
      "status": "ok",
      "timestamp": "2026-03-05T12:00:00Z",
      "database": "ok",
      "indexer": {
        "lastBlock": "6205000",
        "latestBlock": "6205005",
        "lagBlocks": 5
      }
    }

    Orders

    GET/v1/orders

    List all open orders (listings and bids). Supports filtering, sorting, and pagination.

    Parameters

    statusstringFilter by status: OPEN | FULFILLED | CANCELLED
    nftContractstringFilter by NFT contract address
    currencystringFilter by payment token: USDC | USDT | ETH | STRK | WBTC
    sortstringSort field: priceRaw | createdAt
    orderstringasc | desc (default: desc)
    pagenumberPage number (default: 1)
    limitnumberItems per page (default: 20, max: 100)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/orders?status=OPEN&limit=5" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "orderHash": "0x04f7a1...",
          "offerer": "0x0591...",
          "nftContract": "0x05e7...",
          "tokenId": "42",
          "price": "500000",
          "currency": "USDC",
          "status": "OPEN",
          "orderType": "LISTING",
          "createdAt": "2026-03-01T10:00:00Z"
        }
      ],
      "meta": { "total": 128, "page": 1, "limit": 5 }
    }
    GET/v1/orders/:hash

    Get a single order by its on-chain order hash.

    Parameters

    hashstring *The 0x-prefixed order hash

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/orders/0x04f7a1..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "orderHash": "0x04f7a1...",
      "offerer": "0x0591...",
      "nftContract": "0x05e7...",
      "tokenId": "42",
      "price": "500000",
      "currency": "USDC",
      "status": "OPEN"
    }
    GET/v1/orders/token/:contract/:tokenId

    Get all orders for a specific token.

    Parameters

    contractstring *NFT contract address
    tokenIdstring *Token ID

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/orders/token/0x05e7.../42" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [...],
      "meta": { "total": 3, "page": 1, "limit": 20 }
    }
    GET/v1/orders/user/:address

    Get all orders created by a specific user address.

    Parameters

    addressstring *Starknet user address

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/orders/user/0x0591..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [...],
      "meta": { "total": 7, "page": 1, "limit": 20 }
    }

    Minting

    Directly mint assets into existing collections or register new collection contracts. These operations return fully-populated calldata for immediate on-chain execution.

    POST/v1/intents/mint

    Mint an NFT into an existing Medialane collection.

    Parameters

    ownerstring *Collection owner address
    collectionIdstring *Hex or decimal collection ID
    recipientstring *Recipient address
    tokenUristring *IPFS URI or metadata URL
    collectionContractstringOptional: registry contract override

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/mint" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "owner": "0x0591...", "collectionId": "42", "recipient": "0x0591...", "tokenUri": "ipfs://..." }'

    Response

    { "intentId": "clm_mnt123", "status": "SIGNED", "calls": [...] }
    POST/v1/intents/create-collection

    Register a new NFT collection contract on the Medialane registry.

    Parameters

    ownerstring *Requester address
    namestring *Collection name
    symbolstring *Collection symbol
    baseUristring *Base URI for tokens
    collectionContractstringOptional: registry contract override

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/create-collection" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "owner": "0x0591...", "name": "My Collection", "symbol": "MYC", "baseUri": "ipfs://..." }'

    Response

    { "intentId": "clm_coll123", "status": "SIGNED", "calls": [...] }

    Collections

    GET/v1/collections

    List indexed NFT collections with floor price, volume, and token count.

    Parameters

    pagenumberPage number
    limitnumberItems per page
    ownerstringFilter by collection owner address
    isKnownbooleantrue = featured collections only
    sortstring"recent" (default) | "supply" | "floor" | "volume" | "name"

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/collections?owner=0x0591..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "contract": "0x05e7...",
          "collectionId": "1",
          "name": "Mediolano Genesis",
          "owner": "0x0591...",
          "floorPrice": "100000",
          "floorCurrency": "USDC",
          "totalVolume": "5000000",
          "tokenCount": 512
        }
      ],
      "meta": { "total": 14, "page": 1, "limit": 20 }
    }
    GET/v1/collections/:contract

    Get metadata and statistics for a single collection.

    Parameters

    contractstring *NFT contract address

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/collections/0x05e7..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "contract": "0x05e7...",
      "collectionId": "1",
      "name": "Mediolano Genesis",
      "owner": "0x0591...",
      "floorPrice": "100000",
      "totalVolume": "5000000",
      "tokenCount": 512
    }
    GET/v1/collections/:contract/tokens

    List tokens in a collection.

    Parameters

    contractstring *NFT contract address
    pagenumberPage number
    limitnumberItems per page

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/collections/0x05e7.../tokens?limit=10" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [...],
      "meta": { "total": 512, "page": 1, "limit": 10 }
    }

    Tokens

    GET/v1/tokens/owned/:address

    Get all tokens owned by a Starknet address.

    Parameters

    addressstring *Owner's Starknet address

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/tokens/owned/0x0591..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "contract": "0x05e7...",
          "tokenId": "42",
          "owner": "0x0591...",
          "metadata": { "name": "Genesis #42", "image": "ipfs://..." }
        }
      ],
      "meta": { "total": 3, "page": 1, "limit": 20 }
    }
    GET/v1/tokens/:contract/:tokenId

    Get a single token with resolved metadata. Use ?wait=true for JIT metadata resolution.

    Parameters

    contractstring *NFT contract address
    tokenIdstring *Token ID
    waitbooleanIf true, blocks up to 3s to resolve missing metadata

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/tokens/0x05e7.../42?wait=true" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "contract": "0x05e7...",
      "tokenId": "42",
      "owner": "0x0591...",
      "metadata": {
        "name": "Genesis #42",
        "description": "...",
        "image": "ipfs://..."
      }
    }
    GET/v1/tokens/:contract/:tokenId/history

    Get transfer history for a token.

    Parameters

    contractstring *NFT contract address
    tokenIdstring *Token ID

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/tokens/0x05e7.../42/history" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "from": "0x0000...",
          "to": "0x0591...",
          "txHash": "0xabc...",
          "blockNumber": 7000000,
          "timestamp": "2026-03-01T10:00:00Z"
        }
      ]
    }

    Batch Tokens

    Fetch up to 50 tokens in a single request by providing contract+tokenId pairs. More efficient than individual token lookups when hydrating a list or cart.

    GET/v1/tokens/batch

    Fetch multiple tokens by contract and tokenId pairs. Returns the same shape as the single token endpoint but as an array.

    Parameters

    itemsstring *Comma-separated contract:tokenId pairs — e.g. 0x05e7...:1,0x05e7...:2 (max 50 pairs)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/tokens/batch?items=0x05e7...:1,0x05e7...:2" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "contract": "0x05e7...",
          "tokenId": "1",
          "owner": "0x0591...",
          "metadata": { "name": "Genesis #1", "image": "ipfs://..." }
        },
        {
          "contract": "0x05e7...",
          "tokenId": "2",
          "owner": "0x0482...",
          "metadata": { "name": "Genesis #2", "image": "ipfs://..." }
        }
      ]
    }

    Activities

    GET/v1/activities

    List all indexed on-chain events (transfers, sales, listings, cancellations).

    Parameters

    typestringFilter: TRANSFER | SALE | LISTING | CANCEL
    pagenumberPage number
    limitnumberItems per page

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/activities?type=SALE&limit=10" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "type": "SALE",
          "from": "0x0591...",
          "to": "0x0482...",
          "nftContract": "0x05e7...",
          "tokenId": "42",
          "price": "500000",
          "currency": "USDC",
          "txHash": "0xabc...",
          "timestamp": "2026-03-01T10:00:00Z"
        }
      ],
      "meta": { "total": 441, "page": 1, "limit": 10 }
    }
    GET/v1/activities/:address

    Get all activities for a specific user address.

    Parameters

    addressstring *Starknet address

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/activities/0x0591..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [...],
      "meta": { "total": 12, "page": 1, "limit": 20 }
    }

    Intents

    Intents orchestrate SNIP-12 typed data for listings, offers, fulfillments, and cancellations. Create an intent, sign it client-side, then submit the signature.

    POST/v1/intents/listing

    Create a listing intent. Returns typed data for SNIP-12 signing.

    Parameters

    nftContractstring *NFT contract address
    tokenIdstring *Token ID
    pricestring *Price in smallest denomination
    currencystring *USDC | USDT | ETH | STRK | WBTC
    offererstring *Seller Starknet address

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/listing" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "nftContract": "0x05e7...",
        "tokenId": "42",
        "price": "500000",
        "currency": "USDC",
        "offerer": "0x0591..."
      }'

    Response

    {
      "intentId": "clm_abc123",
      "typedData": {
        "types": { ... },
        "primaryType": "Order",
        "domain": { "name": "Medialane", "version": "1", "revision": "1" },
        "message": { ... }
      }
    }
    POST/v1/intents/offer

    Create an offer (bid) intent for a specific token.

    Parameters

    nftContractstring *Target NFT contract
    tokenIdstring *Token ID
    pricestring *Offer amount in smallest denomination
    currencystring *USDC | USDT | ETH | STRK | WBTC
    offererstring *Buyer Starknet address

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/offer" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "nftContract": "0x05e7...", "tokenId": "42", "price": "400000", "currency": "USDC", "offerer": "0x0482..." }'

    Response

    { "intentId": "clm_def456", "typedData": { ... } }
    POST/v1/intents/fulfill

    Create a fulfillment intent to accept an open order.

    Parameters

    orderHashstring *Hash of the order to fulfill
    fulfillerstring *Fulfiller Starknet address

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/fulfill" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "orderHash": "0x04f7a1...", "fulfiller": "0x0482..." }'

    Response

    { "intentId": "clm_ghi789", "typedData": { ... } }
    POST/v1/intents/cancel

    Create a cancellation intent for an open order.

    Parameters

    orderHashstring *Hash of the order to cancel
    offererstring *Original offerer address

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/cancel" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "orderHash": "0x04f7a1...", "offerer": "0x0591..." }'

    Response

    { "intentId": "clm_jkl012", "typedData": { ... } }
    GET/v1/intents/:id

    Get the status of an intent.

    Parameters

    idstring *Intent ID

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/intents/clm_abc123" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "intentId": "clm_abc123",
      "status": "PENDING_SIGNATURE",
      "type": "LISTING"
    }
    PATCH/v1/intents/:id/signature

    Submit the SNIP-12 signature for an intent to trigger on-chain execution.

    Parameters

    idstring *Intent ID
    signaturestring[] *Starknet signature array [r, s]

    * required

    cURL

    curl -X PATCH "https://medialane-backend-production.up.railway.app/v1/intents/clm_abc123/signature" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "signature": ["0xaaa...", "0xbbb..."] }'

    Response

    {
      "intentId": "clm_abc123",
      "status": "SUBMITTED",
      "txHash": "0xabc..."
    }

    Checkout Intent

    Create fulfillment intents for multiple orders in a single request. Useful for cart-style checkout flows. Failed items return an error field rather than aborting the whole batch.

    POST/v1/intents/checkout

    Batch fulfill intent creation. Accepts up to 20 order hashes. Per-item error handling — failed items return { orderHash, error } instead of rejecting the entire request.

    Parameters

    fulfillerstring *Fulfiller Starknet address
    orderHashesstring[] *Array of order hashes to fulfill (max 20)

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/checkout" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "fulfiller": "0x0482...",
        "orderHashes": ["0xabc...", "0xdef..."]
      }'

    Response

    {
      "data": [
        {
          "id": "clm_xyz001",
          "orderHash": "0xabc...",
          "typedData": { "types": { ... }, "primaryType": "Order", "domain": { ... }, "message": { ... } },
          "calls": [...],
          "expiresAt": "2026-03-12T10:15:00Z"
        },
        {
          "orderHash": "0xdef...",
          "error": "Order no longer available"
        }
      ]
    }

    Metadata

    GET/v1/metadata/signed-url

    Get a pre-signed upload URL for pinning metadata to IPFS via Medialane CDN.

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/metadata/signed-url" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "url": "https://ipfs.io/ipfs/...",
      "fields": { ... },
      "expiresAt": "2026-03-01T10:30:00Z"
    }
    POST/v1/metadata/upload

    Upload JSON metadata. Returns an IPFS CID.

    Parameters

    metadataobject *ERC-721 compatible JSON metadata

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/metadata/upload" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "metadata": { "name": "My NFT", "description": "...", "image": "ipfs://..." } }'

    Response

    {
      "cid": "QmXyz...",
      "uri": "ipfs://QmXyz..."
    }
    POST/v1/metadata/upload-file

    Upload a media file. Returns an IPFS CID and gateway URL.

    Parameters

    fileFile (multipart) *Image, audio, or video file

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/metadata/upload-file" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -F "file=@artwork.png"

    Response

    {
      "cid": "QmAbc...",
      "uri": "ipfs://QmAbc...",
      "gateway": "https://gateway.pinata.cloud/ipfs/QmAbc..."
    }
    GET/v1/metadata/resolve

    Resolve and return the metadata JSON for an IPFS URI or on-chain token.

    Parameters

    uristring *ipfs:// URI or https:// URL

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/metadata/resolve?uri=ipfs%3A%2F%2FQmXyz..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "name": "My NFT",
      "description": "...",
      "image": "ipfs://QmAbc..."
    }

    Search

    GET/v1/search

    Full-text search across tokens, collections, and users.

    Parameters

    qstring *Search query string
    typestringFilter: token | collection | user
    limitnumberMax results (default: 10)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/search?q=genesis" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        { "type": "collection", "contract": "0x05e7...", "name": "Mediolano Genesis" },
        { "type": "token", "contract": "0x05e7...", "tokenId": "1", "name": "Genesis #1" }
      ]
    }

    Events (SSE)

    Subscribe to a real-time Server-Sent Events stream for transfers, order lifecycle events, and keepalive pings. Authentication uses a query parameter since browsers cannot send custom headers with the native EventSource API. PREMIUM plan recommended for sustained connections.

    GET/v1/events

    Open a Server-Sent Events stream. The server sends transfer, order.created, order.fulfilled, order.cancelled, and ping (keepalive every 15s) events. Automatically reconnects after 10 minutes via a reconnect event.

    Parameters

    apiKeystring *Your API key (query param — required because EventSource cannot send custom headers)
    sincestringISO 8601 timestamp — resume stream from this point in time

    * required

    cURL

    # Open the stream (cURL streams until closed)
    curl -N "https://medialane-backend-production.up.railway.app/v1/events?apiKey=ml_live_YOUR_KEY"
    
    # Resume from a specific timestamp
    curl -N "https://medialane-backend-production.up.railway.app/v1/events?apiKey=ml_live_YOUR_KEY&since=2026-03-12T10:00:00Z"
    
    # Resume using Last-Event-ID header (standard SSE resume)
    curl -N "https://medialane-backend-production.up.railway.app/v1/events?apiKey=ml_live_YOUR_KEY" \
      -H "Last-Event-ID: evt_abc123"

    Response

    id: evt_001
    event: transfer
    data: {"contractAddress":"0x05e7...","tokenId":"42","from":"0x0000...","to":"0x0591...","txHash":"0xabc...","timestamp":"2026-03-12T10:00:01Z"}
    
    id: evt_002
    event: order.created
    data: {"orderHash":"0x04f7a1...","nftContract":"0x05e7...","tokenId":"42","price":"500000","currency":"USDC","offerer":"0x0591..."}
    
    id: evt_003
    event: order.fulfilled
    data: {"orderHash":"0x04f7a1...","fulfiller":"0x0482...","txHash":"0xdef..."}
    
    id: evt_004
    event: order.cancelled
    data: {"orderHash":"0x04f7a1...","offerer":"0x0591..."}
    
    id: evt_005
    event: ping
    data: {}
    
    event: reconnect
    data: {}

    Browser (native EventSource)

    const url = `https://medialane-backend-production.up.railway.app/v1/events?apiKey=${YOUR_KEY}`
    const source = new EventSource(url)
    
    source.addEventListener("transfer", (e) => {
      const transfer = JSON.parse(e.data)
      console.log("Transfer:", transfer.contractAddress, transfer.tokenId)
    })
    
    source.addEventListener("order.fulfilled", (e) => {
      const order = JSON.parse(e.data)
      console.log("Order fulfilled:", order.orderHash)
    })
    
    source.addEventListener("order.created", (e) => {
      const order = JSON.parse(e.data)
      console.log("New listing:", order.orderHash, order.price, order.currency)
    })
    
    // Reconnect with resume on error
    source.addEventListener("error", () => {
      const lastId = source.lastEventId
      source.close()
      const resumeUrl = `https://medialane-backend-production.up.railway.app/v1/events?apiKey=${YOUR_KEY}${lastId ? `&since=${lastId}` : ""}`
      // reconnect: new EventSource(resumeUrl)
    })

    Node.js (eventsource npm package)

    import EventSource from "eventsource"
    
    const url = `https://medialane-backend-production.up.railway.app/v1/events?apiKey=${YOUR_KEY}`
    const source = new EventSource(url)
    
    source.addEventListener("order.fulfilled", (e) => {
      const order = JSON.parse(e.data)
      console.log("Order fulfilled:", order.orderHash)
    })
    
    source.addEventListener("ping", () => {
      // keepalive — no action needed
    })
    
    source.addEventListener("reconnect", () => {
      // server is closing after 10 min — reconnect
      source.close()
      new EventSource(`https://medialane-backend-production.up.railway.app/v1/events?apiKey=${YOUR_KEY}`)
    })
    
    // Resume from a known point using Last-Event-ID
    const resumeSource = new EventSource(url, {
      headers: { "Last-Event-ID": "evt_abc123" },
    })

    Portal (Self-service)

    Portal endpoints manage your tenant account: API keys, usage stats, and webhooks (PREMIUM). These calls never count toward your monthly quota.

    GET/v1/portal/me

    Get your tenant profile: plan, quota usage, and key count.

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/portal/me" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "tenantId": "tnt_xxx",
      "email": "you@example.com",
      "plan": "FREE",
      "quota": 50,
      "usedThisMonth": 12
    }
    GET/v1/portal/keys

    List all API keys for your account.

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/portal/keys" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        { "id": "key_abc", "name": "Production", "prefix": "ml_live_abc...", "createdAt": "..." }
      ]
    }
    POST/v1/portal/keys

    Create a new API key (max 5 per account).

    Parameters

    namestring *A label for this key

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/portal/keys" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "name": "My Agent Key" }'

    Response

    {
      "id": "key_new",
      "name": "My Agent Key",
      "key": "ml_live_FULL_KEY_SHOWN_ONCE",
      "createdAt": "..."
    }
    DELETE/v1/portal/keys/:id

    Delete an API key. This action is irreversible.

    Parameters

    idstring *Key ID

    * required

    cURL

    curl -X DELETE "https://medialane-backend-production.up.railway.app/v1/portal/keys/key_abc" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    { "success": true }
    GET/v1/portal/usage

    Get 30-day daily usage breakdown.

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/portal/usage" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        { "date": "2026-03-01", "requests": 8 },
        { "date": "2026-02-28", "requests": 4 }
      ]
    }
    GET/v1/portal/usage/recent

    Get the last N request log entries.

    Parameters

    limitnumberMax entries (default: 20)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/portal/usage/recent?limit=5" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        { "method": "GET", "path": "/v1/orders", "status": 200, "ts": "2026-03-01T10:01:00Z" }
      ]
    }
    GET/v1/portal/webhooks

    List registered webhooks. PREMIUM only.

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/portal/webhooks" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        { "id": "wh_abc", "url": "https://yourapp.com/hook", "events": ["ORDER_CREATED"], "active": true }
      ]
    }
    POST/v1/portal/webhooks

    Register a new webhook endpoint. PREMIUM only.

    Parameters

    urlstring *HTTPS endpoint to receive events
    eventsstring[] *ORDER_CREATED | ORDER_FULFILLED | ORDER_CANCELLED | TRANSFER

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/portal/webhooks" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "url": "https://yourapp.com/hook", "events": ["ORDER_CREATED", "TRANSFER"] }'

    Response

    {
      "id": "wh_new",
      "url": "https://yourapp.com/hook",
      "events": ["ORDER_CREATED", "TRANSFER"],
      "secret": "whsec_SHOWN_ONCE"
    }
    DELETE/v1/portal/webhooks/:id

    Delete a webhook.

    Parameters

    idstring *Webhook ID

    * required

    cURL

    curl -X DELETE "https://medialane-backend-production.up.railway.app/v1/portal/webhooks/wh_abc" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    { "success": true }

    Collection Claims

    Claim ownership of an existing Starknet ERC-721 collection. Three verification paths available: automatic on-chain check (requires Clerk JWT), SNIP-12 signature challenge, or manual email review.

    POST/v1/collections/claim

    Path 1: Auto-verify ownership on-chain. Requires both a tenant API key and a Clerk session JWT in the Authorization header. The API checks that the authenticated wallet is the on-chain owner of the contract.

    Parameters

    contractAddressstring *The ERC-721 contract address to claim
    walletAddressstring *The Starknet wallet address claiming ownership

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/collections/claim" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{ "contractAddress": "0x076c...", "walletAddress": "0x03d0..." }'

    Response

    {
      "verified": true,
      "collection": { "contractAddress": "0x076c...", "name": "My Collection", "claimedBy": "0x03d0..." }
    }
    
    // If not verified:
    { "verified": false, "reason": "not_owner" }
    POST/v1/collections/claim/challenge

    Path 2 (step 1): Request a SNIP-12 typed-data challenge for a contract address. Sign the returned typedData with your Starknet wallet, then submit to /verify.

    Parameters

    contractAddressstring *The ERC-721 contract address to claim
    walletAddressstring *The wallet that will sign the challenge

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/collections/claim/challenge" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "contractAddress": "0x076c...", "walletAddress": "0x03d0..." }'

    Response

    {
      "challengeId": "chal_abc123",
      "typedData": { "domain": { "name": "Medialane", "version": "1", "revision": "1" }, "..." },
      "expiresAt": "2026-03-15T16:00:00Z"
    }
    POST/v1/collections/claim/verify

    Path 2 (step 2): Submit the SNIP-12 signature from the challenge step. If valid, the collection is marked as claimed by the wallet.

    Parameters

    challengeIdstring *Challenge ID from /claim/challenge
    signatureobject *{ r: string; s: string } — starknet.js signature object

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/collections/claim/verify" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "challengeId": "chal_abc123", "signature": { "r": "0x...", "s": "0x..." } }'

    Response

    {
      "verified": true,
      "collection": { "contractAddress": "0x076c...", "claimedBy": "0x03d0..." }
    }
    POST/v1/collections/claim/request

    Path 3: Submit a manual claim request for admin review. No wallet signature required — our team will verify and reach out by email.

    Parameters

    contractAddressstring *The ERC-721 contract address to claim
    emailstring *Email address for review correspondence
    walletAddressstringOptional: your Starknet wallet address
    notesstringOptional: context about your connection to the collection

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/collections/claim/request" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "contractAddress": "0x076c...", "email": "creator@example.com", "notes": "I deployed this contract in block 7488000" }'

    Response

    {
      "claim": {
        "id": "clm_xyz",
        "contractAddress": "0x076c...",
        "status": "PENDING",
        "verificationMethod": "MANUAL",
        "createdAt": "2026-03-15T15:00:00Z"
      }
    }

    Profiles

    Enriched display metadata for collections and creators. Collection profiles can only be updated by the wallet that claimed the collection (requires Clerk JWT). Creator profiles can be updated by the profile owner.

    GET/v1/collections/:contract/profile

    Get the display profile for a collection (displayName, description, cover image, banner, social links). Returns null if no profile has been set.

    Parameters

    contractstring *NFT contract address

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/collections/0x076c.../profile" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "contractAddress": "0x076c...",
      "chain": "STARKNET",
      "displayName": "The Revenge of Shiroi",
      "description": "Music · Video · Concept Art",
      "image": "ipfs://bafybeif2a...",
      "bannerImage": "ipfs://bafybeic...",
      "websiteUrl": "https://shiroi.io",
      "twitterUrl": "https://x.com/shiroi",
      "discordUrl": null,
      "telegramUrl": null,
      "updatedAt": "2026-03-15T15:00:00Z"
    }
    PATCH/v1/collections/:contract/profile

    Update the display profile for a collection. Requires a Clerk session JWT — the authenticated wallet must be the claimedBy address for this collection.

    Parameters

    contractstring *NFT contract address (URL param)
    displayNamestringDisplay name (overrides on-chain name)
    descriptionstringCollection description
    imagestringCover image IPFS URI (ipfs://...)
    bannerImagestringBanner image IPFS URI (ipfs://...)
    websiteUrlstringWebsite URL
    twitterUrlstringTwitter/X URL
    discordUrlstringDiscord server URL
    telegramUrlstringTelegram URL

    * required

    cURL

    curl -X PATCH "https://medialane-backend-production.up.railway.app/v1/collections/0x076c.../profile" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{ "displayName": "Shiroi Collection", "description": "Music & Concept Art", "websiteUrl": "https://shiroi.io" }'

    Response

    {
      "contractAddress": "0x076c...",
      "displayName": "Shiroi Collection",
      "description": "Music & Concept Art",
      "websiteUrl": "https://shiroi.io",
      "updatedAt": "2026-03-15T15:05:00Z"
    }
    GET/v1/creators/:wallet/profile

    Get the display profile for a creator wallet (displayName, bio, avatar, banner, social links). Returns null if no profile has been set.

    Parameters

    walletstring *Starknet wallet address

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/creators/0x03d0.../profile" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "walletAddress": "0x03d0...",
      "chain": "STARKNET",
      "displayName": "Kalamaha",
      "bio": "Visual artist on Starknet",
      "avatarImage": "ipfs://bafkrei...",
      "bannerImage": null,
      "websiteUrl": "https://kalamaha.art",
      "twitterUrl": "https://x.com/kalamaha",
      "discordUrl": null,
      "telegramUrl": null,
      "updatedAt": "2026-03-15T15:00:00Z"
    }
    PATCH/v1/creators/:wallet/profile

    Update a creator profile. Requires a Clerk session JWT — the authenticated wallet must match the wallet URL parameter.

    Parameters

    walletstring *Starknet wallet address (URL param)
    displayNamestringDisplay name or handle
    biostringShort bio
    avatarImagestringAvatar IPFS URI (ipfs://...)
    bannerImagestringBanner IPFS URI (ipfs://...)
    websiteUrlstringWebsite URL
    twitterUrlstringTwitter/X URL
    discordUrlstringDiscord URL
    telegramUrlstringTelegram URL

    * required

    cURL

    curl -X PATCH "https://medialane-backend-production.up.railway.app/v1/creators/0x03d0.../profile" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{ "displayName": "Kalamaha", "bio": "Visual artist on Starknet" }'

    Response

    {
      "walletAddress": "0x03d0...",
      "displayName": "Kalamaha",
      "bio": "Visual artist on Starknet",
      "updatedAt": "2026-03-15T15:05:00Z"
    }

    On-chain Comments

    Permanent on-chain comments posted to the NFTComments contract on Starknet. Comments are indexed by the backend and surfaced here. The Cairo contract enforces a 60-second per-address rate limit and comments cannot be deleted on-chain, only hidden at the application layer after reports.

    GET/v1/tokens/:contract/:tokenId/comments

    List indexed on-chain comments for a token, newest first. Hidden comments (3+ reports) are excluded.

    Parameters

    contractstring *NFT contract address
    tokenIdstring *Token ID
    pagenumberPage number (default: 1)
    limitnumberResults per page (default: 20, max: 100)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/tokens/0x05e7.../42/comments?limit=20" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "id": "cmt_01j...",
          "chain": "starknet",
          "contractAddress": "0x05e7...",
          "tokenId": "42",
          "author": "0x03d0...",
          "content": "This is a permanent mark on Starknet.",
          "txHash": "0x07a2...",
          "blockNumber": "789123",
          "postedAt": "2026-03-22T14:00:00Z"
        }
      ],
      "meta": { "page": 1, "limit": 20, "total": 5 }
    }

    Counter-offers

    Sellers can respond to buyer bids with a counter-offer — a new on-chain listing linked to the original bid. The original bid is marked COUNTER_OFFERED. The buyer can then accept (fulfill the counter listing) or ignore it.

    GET/v1/orders/counter-offers

    List counter-offer listings. Pass originalOrderHash for the buyer's view (one counter per bid) or sellerAddress for the seller's view (all counters they have sent). At least one query param is required.

    Parameters

    originalOrderHashstringOriginal bid order hash — returns the counter-offer for this specific bid
    sellerAddressstringSeller address — returns all counter-offers sent by this seller
    pagenumberPage number (default: 1)
    limitnumberResults per page (default: 20)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/orders/counter-offers?originalOrderHash=0x04f7a1..." \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "id": "ord_01j...",
          "orderHash": "0x0a1b...",
          "offerer": "0x0591...",
          "status": "ACTIVE",
          "parentOrderHash": "0x04f7a1...",
          "counterOfferMessage": "Best I can do!",
          "price": { "raw": "750000", "formatted": "0.75", "currency": "USDC", "decimals": 6 },
          "endTime": "2026-03-25T00:00:00Z",
          "token": { "name": "Genesis #42", "image": "ipfs://...", "description": null }
        }
      ],
      "meta": { "page": 1, "limit": 20, "total": 1 }
    }
    POST/v1/intents/counter-offer

    Create a counter-offer intent. The seller proposes a new price for the NFT in response to a buyer's active bid. Currency is derived server-side from the original bid token — do not pass a currency field. Requires a Clerk session JWT for authentication; the seller address must match the consideration.recipient of the original bid.

    Parameters

    sellerAddressstring *Seller's wallet address
    originalOrderHashstring *Order hash of the original buyer bid
    counterPricestring *Counter price as raw wei integer string
    durationSecondsnumber *Validity duration in seconds (3600–2592000)
    messagestringOptional seller message to buyer (max 500 chars)

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/intents/counter-offer" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{
        "sellerAddress": "0x0591...",
        "originalOrderHash": "0x04f7a1...",
        "counterPrice": "750000",
        "durationSeconds": 86400,
        "message": "Best I can do!"
      }'

    Response

    {
      "data": {
        "id": "int_01j...",
        "typedData": { ... },
        "calls": [ ... ],
        "expiresAt": "2026-03-25T00:00:00Z"
      }
    }

    Remix Licensing

    Creators can allow others to remix their NFTs under specific license terms. Open licenses (CC0, CC BY, CC BY-SA, CC BY-NC) are auto-approved. Custom terms require creator approval before the requester can mint. All endpoints require a Clerk JWT except the public remixes list.

    GET/v1/tokens/:contract/:tokenId/remixes

    List public remixes of a token. Price and currency fields are omitted — this is a public endpoint. Returns minted remixes only.

    Parameters

    contractstring *Original NFT contract address
    tokenIdstring *Original token ID
    pagenumberPage number (default: 1)
    limitnumberResults per page (default: 20)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/tokens/0x05e7.../42/remixes" \
      -H "x-api-key: ml_live_YOUR_KEY"

    Response

    {
      "data": [
        {
          "id": "rxo_01j...",
          "remixContract": "0x06a3...",
          "remixTokenId": "1",
          "licenseType": "CC BY",
          "commercial": true,
          "derivatives": true,
          "createdAt": "2026-03-23T10:00:00Z"
        }
      ],
      "meta": { "page": 1, "limit": 20, "total": 3 }
    }
    POST/v1/remix-offers

    Submit a custom remix offer for a token. If the token's license is not open (CC0/CC BY/CC BY-SA/CC BY-NC), the creator must approve before the requester can mint. Requires Clerk JWT.

    Parameters

    originalContractstring *Original NFT contract address
    originalTokenIdstring *Original token ID
    licenseTypestring *Requested license (e.g. CC BY-NC)
    commercialboolean *Commercial use requested
    derivativesboolean *Derivatives allowed
    royaltyPctnumberRoyalty percentage (0–100)
    proposedPricestringProposed payment as raw wei integer string
    proposedCurrencystringToken address of proposed payment currency
    messagestringOptional message to the creator

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/remix-offers" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{
        "originalContract": "0x05e7...",
        "originalTokenId": "42",
        "licenseType": "CC BY-NC",
        "commercial": false,
        "derivatives": true,
        "royaltyPct": 10,
        "message": "Would love to remix this for my EP cover"
      }'

    Response

    {
      "data": {
        "id": "rxo_01j...",
        "status": "PENDING",
        "originalContract": "0x05e7...",
        "originalTokenId": "42",
        "creatorAddress": "0x0591...",
        "requesterAddress": "0x03d0...",
        "licenseType": "CC BY-NC",
        "commercial": false,
        "derivatives": true,
        "royaltyPct": 10,
        "approvedCollection": null,
        "remixContract": null,
        "remixTokenId": null,
        "orderHash": null,
        "expiresAt": "2026-04-23T10:00:00Z",
        "createdAt": "2026-03-23T10:00:00Z"
      }
    }
    POST/v1/remix-offers/auto

    Submit an auto remix offer for a token with an open license (CC0, CC BY, CC BY-SA, CC BY-NC). Auto-approved immediately — no creator action needed. Requires Clerk JWT.

    Parameters

    originalContractstring *Original NFT contract address
    originalTokenIdstring *Original token ID
    licenseTypestring *Open license type (must be CC0, CC BY, CC BY-SA, or CC BY-NC)

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/remix-offers/auto" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{ "originalContract": "0x05e7...", "originalTokenId": "7", "licenseType": "CC0" }'

    Response

    { "data": { "id": "rxo_01j...", "status": "AUTO_PENDING", ... } }
    POST/v1/remix-offers/self/confirm

    Record a self-remix — the token owner remixing their own asset. Call after the remix has been minted on-chain. Requires Clerk JWT.

    Parameters

    originalContractstring *Original NFT contract address
    originalTokenIdstring *Original token ID
    remixContractstring *Remix NFT contract address
    remixTokenIdstring *Remix token ID
    licenseTypestring *License type applied to the remix
    commercialboolean *Commercial use
    derivativesboolean *Further derivatives allowed
    royaltyPctnumberRoyalty percentage

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/remix-offers/self/confirm" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{ "originalContract": "0x05e7...", "originalTokenId": "42", "remixContract": "0x06a3...", "remixTokenId": "1", "licenseType": "CC BY", "commercial": true, "derivatives": true }'

    Response

    { "data": { "id": "rxo_01j...", "status": "SELF_MINTED", ... } }
    GET/v1/remix-offers

    List remix offers for the authenticated user. Pass role=creator to see incoming offers (you are the original creator), or role=requester to see offers you made. Requires Clerk JWT.

    Parameters

    rolestring *"creator" or "requester"
    pagenumberPage (default: 1)
    limitnumberResults per page (default: 20)

    * required

    cURL

    curl "https://medialane-backend-production.up.railway.app/v1/remix-offers?role=creator" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT"

    Response

    {
      "data": [ { "id": "rxo_01j...", "status": "PENDING", "requesterAddress": "0x03d0...", ... } ],
      "meta": { "page": 1, "limit": 20, "total": 2 }
    }
    POST/v1/remix-offers/:id/confirm

    Creator approves a pending remix offer and records the minted remix on-chain coordinates. Requires Clerk JWT — caller must be the creator of the original token.

    Parameters

    idstring *Remix offer ID (URL param)
    approvedCollectionstring *Collection contract where the remix will be minted
    remixContractstring *Remix NFT contract address (usually same as approvedCollection)
    remixTokenIdstring *Minted remix token ID
    orderHashstringMarketplace order hash if a payment was arranged

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/remix-offers/rxo_01j.../confirm" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT" \
      -H "Content-Type: application/json" \
      -d '{ "approvedCollection": "0x06a3...", "remixContract": "0x06a3...", "remixTokenId": "1" }'

    Response

    { "data": { "id": "rxo_01j...", "status": "APPROVED", "remixContract": "0x06a3...", "remixTokenId": "1", ... } }
    POST/v1/remix-offers/:id/reject

    Creator rejects a pending remix offer. Requires Clerk JWT — caller must be the creator of the original token.

    Parameters

    idstring *Remix offer ID (URL param)

    * required

    cURL

    curl -X POST "https://medialane-backend-production.up.railway.app/v1/remix-offers/rxo_01j.../reject" \
      -H "x-api-key: ml_live_YOUR_KEY" \
      -H "Authorization: Bearer CLERK_SESSION_JWT"

    Response

    { "data": { "id": "rxo_01j...", "status": "REJECTED", ... } }

    Technical Details

    SNIP-12 Domain

    Medialane uses SNIP-12 for off-chain message signing. If you are building your own signer, use the following domain:

    ts
    {
      "name": "Medialane",
      "version": "1",
      "revision": "1"
    }

    Address Normalization

    The API normalizes all addresses server-side to 64-character lowercase hex strings (prefixed with 0x). You can pass any valid Starknet address format — short, long, or mixed-case — and the API will handle normalization automatically. The @medialane/sdk also normalizes addresses before every API call.