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
    SDK

    @medialane/sdk

    Framework-agnostic TypeScript SDK for the Medialane API. Bundles a full REST client and on-chain marketplace helpers in one package.

    Install

    bash
    # bun
    bun add @medialane/sdk starknet
    
    # npm
    npm install @medialane/sdk starknet
    
    # yarn
    yarn add @medialane/sdk starknet

    Peer dependency: starknet@^6

    Configure

    Create a MedialaneClient with your network and API key.

    ts
    import { MedialaneClient } from "@medialane/sdk"
    
    const client = new MedialaneClient({
      network: "mainnet",        // "mainnet" | "sepolia"
      rpcUrl: "https://starknet-mainnet.g.alchemy.com/starknet/version/rpc/v0_7/YOUR_KEY",
      backendUrl: "https://medialane-backend-production.up.railway.app",
      apiKey: "ml_live_YOUR_KEY",
      marketplaceContract: "0x04299b51289aa700de4ce19cc77bcea8430bfd1aef04193efab09d60a3a7ee0f",
      // Optional: configure retry for transient failures
      retryOptions: {
        maxAttempts: 3,      // default
        baseDelayMs: 300,    // default
        maxDelayMs: 5000,    // default
      },
    })

    The apiKey is sent as x-api-key on every request. Get your key at /account.

    Minting & Launchpad

    The SDK provides two ways to mint assets: direct on-chain calls (requires signer) and backend-orchestrated intents.

    Mint an asset into a collection

    ts
    // 1. Direct on-chain (client.marketplace)
    await client.marketplace.mint(account, {
      collectionId: "42",
      recipient: "0x0591...",
      tokenUri: "ipfs://...",
    })
    
    // 2. Via backend intent (client.api)
    // No SNIP-12 signing required for mint/create-collection intents
    const { intentId, calls } = await client.api.createMintIntent({
      owner: "0x0591...", // collection owner
      collectionId: "42",
      recipient: "0x0592...",
      tokenUri: "ipfs://...",
    })

    Register a new collection

    ts
    // 1. Direct on-chain
    await client.marketplace.createCollection(account, {
      name: "My Collection",
      symbol: "MYC",
      baseUri: "ipfs://...",
    })
    
    // 2. Via backend intent
    const { intentId, calls } = await client.api.createCollectionIntent({
      owner: "0x0591...",
      name: "My Collection",
      symbol: "MYC",
      baseUri: "ipfs://...",
    })

    Marketplace (on-chain)

    client.marketplace provides typed wrappers for direct contract calls via starknet.js.

    ts
    // Get order details directly from the contract
    const order = await client.marketplace.getOrderDetails("0x04f7a1...")
    
    // Get the current nonce for signing
    const nonce = await client.marketplace.getNonce("0x0591...")

    API Client (REST)

    client.api mirrors the full REST API surface.

    List open orders

    ts
    const orders = await client.api.getOrders({ status: "ACTIVE", limit: 20 })
    
    console.log(orders.data[0].orderHash, orders.data[0].price)

    Get a token with metadata

    ts
    const token = await client.api.getToken("0x05e7...", "42")
    
    console.log(token.data.metadata?.name)

    Get collections by owner

    ts
    // Fetch collections owned by a wallet address
    // Addresses are normalized automatically — pass any valid Starknet format
    const result = await client.api.getCollectionsByOwner("0x0591...")
    result.data.forEach((col) => {
      console.log(col.name, col.collectionId) // collectionId = on-chain registry ID
    })

    Create a listing intent

    ts
    import { toSignatureArray } from "@medialane/sdk"
    
    // 1. Create the intent — get typed data back
    const intent = await client.api.createListingIntent({
      nftContract: "0x05e7...",
      tokenId: "42",
      price: "500000",
      currency: "USDC",
      offerer: walletAddress,
      endTime: Math.floor(Date.now() / 1000) + 86400 * 30,
    })
    
    // 2. Sign with starknet.js
    import { Account } from "starknet"
    const account = new Account(provider, walletAddress, privateKey)
    const signature = await account.signMessage(intent.data.typedData)
    
    // 3. Submit the signature
    await client.api.submitIntentSignature(intent.data.id, toSignatureArray(signature))

    Search

    ts
    const results = await client.api.search("genesis", 10)
    results.data.tokens.forEach((t) => console.log(t.metadata?.name))
    results.data.collections.forEach((c) => console.log(c.name))

    Portal — manage keys

    ts
    // List your API keys
    const keys = await client.api.getApiKeys()
    
    // Create a new key
    const newKey = await client.api.createApiKey("Agent Key")
    console.log(newKey.data.key) // shown once — save it!
    
    // Get usage
    const usage = await client.api.getUsage()

    On-chain comments

    ts
    // Fetch permanent on-chain comments for a token
    const result = await client.api.getTokenComments("0x05e7...", "42", { limit: 20 })
    result.data.forEach((c) => {
      console.log(c.author, c.content, c.postedAt)
    })

    Counter-offers

    ts
    // Seller creates a counter-offer in response to a buyer's bid
    const intent = await client.api.createCounterOfferIntent(
      {
        sellerAddress: "0x0591...",
        originalOrderHash: "0x04f7a1...",
        counterPrice: "750000",       // raw wei
        durationSeconds: 86400,       // 1 day
        message: "Best I can do!",
      },
      clerkToken
    )
    
    // Buyer fetches counter-offers for their bid
    const counters = await client.api.getCounterOffers({
      originalOrderHash: "0x04f7a1...",
    })
    console.log(counters.data[0].price)
    
    // Buyer accepts by fulfilling the counter-offer (it is a standard listing)
    await client.api.createFulfillIntent({ fulfiller: buyerAddress, orderHash: counters.data[0].orderHash })

    Remix licensing

    ts
    import { OPEN_LICENSES } from "@medialane/sdk"
    
    // Check if a license is open (auto-approved remix)
    console.log(OPEN_LICENSES) // ["CC0", "CC BY", "CC BY-SA", "CC BY-NC"]
    
    // Request permission to remix a token (custom offer, Clerk JWT required)
    const offer = await client.api.submitRemixOffer(
      {
        originalContract: "0x05e7...",
        originalTokenId: "42",
        licenseType: "CC BY-NC",
        commercial: false,
        derivatives: true,
        royaltyPct: 10,
        message: "Would love to remix this for my EP cover",
      },
      clerkToken
    )
    
    // Open-license tokens are auto-approved
    const autoOffer = await client.api.submitAutoRemixOffer(
      { originalContract: "0x05e7...", originalTokenId: "7", licenseType: "CC0" },
      clerkToken
    )
    
    // Creator approves a pending offer
    await client.api.confirmRemixOffer(offer.data.id, {
      approvedCollection: "0x06a3...",
      remixContract: "0x06a3...",
      remixTokenId: "1",
    }, clerkToken)
    
    // Creator rejects an offer
    await client.api.rejectRemixOffer(offer.data.id, clerkToken)
    
    // Owner records their own self-remix after minting
    await client.api.confirmSelfRemix(
      {
        originalContract: "0x05e7...",
        originalTokenId: "42",
        remixContract: "0x06a3...",
        remixTokenId: "1",
        licenseType: "CC BY",
        commercial: true,
        derivatives: true,
      },
      clerkToken
    )
    
    // List incoming / outgoing offers
    const incoming = await client.api.getRemixOffers({ role: "creator" }, clerkToken)
    const outgoing = await client.api.getRemixOffers({ role: "requester" }, clerkToken)
    
    // Get public remixes for a token (no auth needed)
    const remixes = await client.api.getTokenRemixes("0x05e7...", "42")
    remixes.data.forEach((r) => console.log(r.remixContract, r.remixTokenId, r.licenseType))

    CollectionSort — typed sort options

    ts
    import type { CollectionSort } from "@medialane/sdk"
    
    // "recent" | "supply" | "floor" | "volume" | "name"
    const sort: CollectionSort = "floor"
    await client.api.getCollections(1, 20, true, sort)

    Error Handling

    The SDK throws MedialaneError for marketplace issues and MedialaneApiError for REST API failures. Both carry a typed .code field from the MedialaneErrorCode union.

    ts
    import { MedialaneError, MedialaneApiError } from "@medialane/sdk"
    
    try {
      await client.marketplace.mint(account, params)
    } catch (err) {
      if (err instanceof MedialaneError) {
        console.error(err.code, err.message) // e.g. "TRANSACTION_FAILED"
      }
      if (err instanceof MedialaneApiError) {
        console.error(err.code, err.status, err.message) // e.g. "TOKEN_NOT_FOUND", 404
      }
    }

    Error Codes

    All errors expose a MedialaneErrorCode typed union:

    ts
    type MedialaneErrorCode =
      | "TOKEN_NOT_FOUND"
      | "COLLECTION_NOT_FOUND"
      | "ORDER_NOT_FOUND"
      | "INTENT_NOT_FOUND"
      | "INTENT_EXPIRED"
      | "RATE_LIMITED"
      | "NETWORK_NOT_SUPPORTED"
      | "APPROVAL_FAILED"
      | "TRANSACTION_FAILED"
      | "INVALID_PARAMS"
      | "UNAUTHORIZED"
      | "UNKNOWN"
    CodeTrigger
    TOKEN_NOT_FOUND404 response or missing token
    COLLECTION_NOT_FOUND404 on collection lookup
    ORDER_NOT_FOUND404 on order lookup
    INTENT_NOT_FOUND404 on intent lookup
    INTENT_EXPIRED410 response — intent TTL exceeded
    RATE_LIMITED429 response — too many requests
    NETWORK_NOT_SUPPORTEDSepolia selected with no contract addresses
    APPROVAL_FAILEDNFT approval missing before listing
    TRANSACTION_FAILEDOn-chain call reverted
    INVALID_PARAMS400 response — bad request parameters
    UNAUTHORIZED401/403 — missing or invalid API key
    UNKNOWNUnexpected errors

    Note: 4xx errors are not retried automatically. Only transient network and 5xx errors trigger the retry logic configured via retryOptions.

    Full API reference — all REST endpoints, parameters, and response schemas are documented in the API Reference.