Skip to content

REST API Gateway

Structured, indexed Sui blockchain data with powerful filtering, pagination, and search capabilities.

Overview

The REST API provides access to processed and indexed blockchain data with automatic fallback to live RPC calls for comprehensive support.

Base URL: https://api.inodra.com/v1/

Note: Only the endpoints listed in API Reference → REST are implemented in this repo. The examples below are illustrative patterns; adjust to available routes.

Getting Started

1. Basic Setup

javascript
const BASE_URL = 'https://api.inodra.com/v1'
const API_KEY = 'YOUR_API_KEY'

async function apiCall(endpoint) {
  const response = await fetch(`${BASE_URL}${endpoint}`, {
    headers: {
      'x-api-key': API_KEY
    }
  })

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }

  return response.json()
}

2. Your First Request

javascript
// Get latest checkpoints
const checkpoints = await apiCall('/checkpoints?limit=10')
console.log('Latest checkpoints:', checkpoints.data)

// Get specific checkpoint
const checkpoint = await apiCall('/checkpoints/12345678')
console.log('Checkpoint details:', checkpoint)

Common Operations

Get Checkpoints

javascript
// List checkpoints with filtering
const checkpoints = await apiCall('/checkpoints?limit=20&order=desc&from_sequence=12345000')

// Get specific checkpoint
const checkpoint = await apiCall('/checkpoints/12345678')

Query Transactions

javascript
// List transactions with filters
const transactions = await apiCall(
  `/transactions?${new URLSearchParams({
    limit: 50,
    sender: '0x123...',
    from_checkpoint: 12345000,
    to_checkpoint: 12345100
  })}`
)

// Get specific transaction with full details
const transaction = await apiCall(
  '/transactions/ABC123...?expand=effects,events,object_changes,balance_changes'
)

Address Operations

javascript
// Get address balance
const balance = await apiCall('/addresses/0x123.../balance')

// Get address balance for specific coin
const suiBalance = await apiCall('/addresses/0x123.../balance?coin_type=0x2::sui::SUI')

// Get objects owned by address
const objects = await apiCall('/addresses/0x123.../objects?limit=50&type=0x2::coin::Coin')

// Get address transaction history
const txHistory = await apiCall('/addresses/0x123.../transactions?limit=100&role=sender')

Object Queries

javascript
// Search objects by owner and type
const objects = await apiCall(
  `/objects?${new URLSearchParams({
    owner: '0x123...',
    type: '0x2::coin::Coin<0x2::sui::SUI>',
    limit: 50
  })}`
)

// Get specific object details
const object = await apiCall('/objects/0x456...?expand=content,display,owner')

Event Queries

javascript
// Query events by type
const events = await apiCall(
  `/events?${new URLSearchParams({
    package_id: '0x2',
    module: 'coin',
    event_type: 'Transfer',
    limit: 100,
    from_checkpoint: 12345000
  })}`
)

// Query events by sender
const senderEvents = await apiCall('/events?sender=0x123...&limit=50')

Coin Information

javascript
// Get coin metadata
const coinMeta = await apiCall('/coins/0x2::sui::SUI/metadata')

// List all coins by creator
const coins = await apiCall('/coins?creator=0x2&limit=50')

Advanced Filtering

Time-based Filtering

javascript
// Transactions in specific time range
const recentTx = await apiCall(
  `/transactions?${new URLSearchParams({
    from_timestamp: Date.now() - 24 * 60 * 60 * 1000, // 24 hours ago
    to_timestamp: Date.now(),
    limit: 100
  })}`
)

Complex Object Queries

javascript
// NFTs owned by specific address
const nfts = await apiCall(
  `/objects?${new URLSearchParams({
    owner: '0x123...',
    type: '0x2::nft::NFT',
    limit: 100
  })}`
)

// Coins of specific type with minimum balance
const largeCoinObjects = await apiCall(
  `/objects?${new URLSearchParams({
    type: '0x2::coin::Coin<0x2::sui::SUI>',
    min_balance: '1000000000', // 1 SUI
    limit: 50
  })}`
)

Event Filtering

javascript
// Events from specific package and module
const moduleEvents = await apiCall(
  `/events?${new URLSearchParams({
    package_id: '0x2',
    module: 'coin',
    from_checkpoint: 12000000,
    limit: 200
  })}`
)

// Events affecting specific address
const addressEvents = await apiCall(
  `/events?${new URLSearchParams({
    affected_address: '0x123...',
    event_type: 'Transfer',
    limit: 100
  })}`
)

Pagination Patterns

Offset-based Pagination

javascript
async function getAllTransactions(batchSize = 100) {
  let allTransactions = []
  let offset = 0
  let hasMore = true

  while (hasMore) {
    const response = await apiCall(`/transactions?limit=${batchSize}&offset=${offset}`)

    allTransactions.push(...response.data)

    hasMore = response.pagination.hasNextPage
    offset += batchSize
  }

  return allTransactions
}

Cursor-based Pagination

javascript
async function getAllEvents(eventType) {
  let allEvents = []
  let cursor = null
  let hasMore = true

  while (hasMore) {
    const params = new URLSearchParams({
      event_type: eventType,
      limit: 100
    })

    if (cursor) {
      params.append('cursor', cursor)
    }

    const response = await apiCall(`/events?${params}`)

    allEvents.push(...response.data)

    hasMore = response.pagination.hasNextPage
    cursor = response.pagination.cursor
  }

  return allEvents
}

Error Handling

javascript
async function safeApiCall(endpoint, retries = 3) {
  for (let i = 0; i <= retries; i++) {
    try {
      const response = await fetch(`${BASE_URL}${endpoint}`, {
        headers: {
          'x-api-key': API_KEY
        }
      })

      // Handle backoff for 429 responses
      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('retry-after') || '60')
        if (i < retries) {
          await sleep(retryAfter * 1000)
          continue
        }
      }

      if (!response.ok) {
        const error = await response.json()
        throw new Error(`API Error: ${error.error.message}`)
      }

      const data = await response.json()

      // Optional: log response metadata

      return data
    } catch (error) {
      if (i === retries) throw error

      // Exponential backoff
      await sleep(Math.pow(2, i) * 1000)
    }
  }
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

Real-time Updates with Polling

javascript
class RealTimeMonitor {
  constructor(apiKey) {
    this.apiKey = apiKey
    this.lastCheckpoint = null
    this.subscribers = []
  }

  subscribe(callback) {
    this.subscribers.push(callback)
  }

  async start(interval = 5000) {
    this.intervalId = setInterval(async () => {
      try {
        const response = await apiCall('/checkpoints?limit=1')
        const latestCheckpoint = response.data[0]

        if (
          !this.lastCheckpoint ||
          latestCheckpoint.sequenceNumber > this.lastCheckpoint.sequenceNumber
        ) {
          this.lastCheckpoint = latestCheckpoint

          // Notify subscribers
          this.subscribers.forEach((callback) => {
            callback('checkpoint', latestCheckpoint)
          })
        }
      } catch (error) {
        console.error('Polling error:', error)
      }
    }, interval)
  }

  stop() {
    if (this.intervalId) {
      clearInterval(this.intervalId)
    }
  }
}

// Usage
const monitor = new RealTimeMonitor(API_KEY)
monitor.subscribe((type, data) => {
  console.log('New checkpoint:', data.sequenceNumber)
})
monitor.start()

Response Caching

javascript
class CachedAPIClient {
  constructor(apiKey, defaultTTL = 60000) {
    this.apiKey = apiKey
    this.cache = new Map()
    this.defaultTTL = defaultTTL
  }

  async get(endpoint, ttl = this.defaultTTL) {
    const cacheKey = endpoint
    const cached = this.cache.get(cacheKey)

    if (cached && Date.now() - cached.timestamp < ttl) {
      console.log('Cache hit:', endpoint)
      return cached.data
    }

    console.log('Cache miss:', endpoint)
    const data = await apiCall(endpoint)

    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    })

    return data
  }

  clearCache() {
    this.cache.clear()
  }
}

// Usage
const client = new CachedAPIClient(API_KEY)

// This will make an API call
const checkpoints1 = await client.get('/checkpoints?limit=10')

// This will use cached data (within 60 seconds)
const checkpoints2 = await client.get('/checkpoints?limit=10')

OpenAPI Integration

javascript
// Using OpenAPI spec for type generation
import { DefaultApi, Configuration } from './generated/api'

const config = new Configuration({
  basePath: 'https://api.inodra.com/v1',
  apiKey: 'YOUR_API_KEY'
})

const api = new DefaultApi(config)

// Type-safe API calls
const checkpoints = await api.getCheckpoints({ limit: 10, order: 'desc' })
const transaction = await api.getTransaction({
  digest: 'ABC123...',
  expand: 'effects,events'
})

Monitoring and Analytics

javascript
class APIUsageTracker {
  constructor() {
    this.requests = []
    this.startTime = Date.now()
  }

  logRequest(endpoint, responseTime, status) {
    this.requests.push({
      endpoint,
      responseTime,
      status,
      timestamp: Date.now()
    })
  }

  getStats() {
    const now = Date.now()
    const recentRequests = this.requests.filter(
      (req) => now - req.timestamp < 60000 // Last minute
    )

    const avgResponseTime =
      recentRequests.reduce((sum, req) => sum + req.responseTime, 0) / recentRequests.length

    const errorRate =
      recentRequests.filter((req) => req.status >= 400).length / recentRequests.length

    return {
      totalRequests: this.requests.length,
      recentRequestsPerMinute: recentRequests.length,
      avgResponseTime: avgResponseTime || 0,
      errorRate: errorRate || 0
    }
  }
}

// Wrap API calls with tracking
const tracker = new APIUsageTracker()

async function trackedApiCall(endpoint) {
  const startTime = Date.now()

  try {
    const result = await apiCall(endpoint)
    const responseTime = Date.now() - startTime

    tracker.logRequest(endpoint, responseTime, 200)
    return result
  } catch (error) {
    const responseTime = Date.now() - startTime
    tracker.logRequest(endpoint, responseTime, error.status || 500)
    throw error
  }
}

Advantages of REST Gateway

📊 Structured Data

  • Pre-processed and indexed blockchain data
  • Optimized for filtering and analytics
  • Fast response times for complex queries
  • Multi-field filtering and sorting
  • Full-text search capabilities
  • Aggregated statistics and metrics

📈 Analytics Ready

  • Historical data analysis
  • Trend identification
  • Business intelligence integration

🌐 Web-friendly

  • Standard HTTP methods and status codes
  • JSON responses
  • Easy integration with web frameworks

When to Use REST Gateway

Perfect for:

  • Web applications and dashboards
  • Analytics and reporting systems
  • Mobile applications
  • Integration with existing REST-based systems
  • Data analysis and business intelligence

Interactive Documentation

Explore the complete REST API at:

Next Steps

Ready to analyze blockchain data? Start querying! 🚀

Released under the MIT License.