{"templateId":"markdown","sharedDataIds":{},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Best Practices","description":"Sprinklr offers robust, convenient, and simple RESTful Web service APIs to integrate data from Sprinklr to any external system.","keywords":["Sprinklr API","Sprinklr Developer Portal","OAuth 2.0","RESTful API","Social Media API","Customer Experience API","Unified-CXM","Social Listening API"]},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"best-practices","__idx":0},"children":["Best Practices"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Building a production-ready integration with the Sprinklr API requires more than just making successful API calls. This guide shares battle-tested patterns, common pitfalls to avoid, and techniques that separate hobby projects from enterprise-grade integrations."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"why-best-practices-matter","__idx":1},"children":["Why best practices matter"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The difference between a working integration and a reliable one often comes down to how you handle the details:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["A working integration"]}," makes API calls and gets data back"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["A reliable integration"]}," handles errors gracefully, respects rate limits, protects credentials, scales efficiently, and degrades gracefully under load"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Following these best practices will help you build integrations that:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Don't wake you up at 3 AM with production incidents"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Scale from 10 requests/day to 10,000 without rewriting"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Handle errors without losing data"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Perform efficiently and stay under rate limits"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Are maintainable by your team (and future you)"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Let's dive into the patterns that make this possible."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"authentication--security","__idx":2},"children":["Authentication & security"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Security isn't optional—it's fundamental. A single exposed credential can compromise your entire integration and your customers' data."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"store-credentials-securely","__idx":3},"children":["Store credentials securely"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["❌ Never do this:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// DANGER: Credentials in code\nconst accessToken = \"SPRD_tjACKHVlwb3D7Xd2WDZI3VxgBnI3Lmk2mwFfZasVpkDNzZSRzRp6lbHpxLqOCJ8Gomp6iYXkl4JGLr91cpUxfw==\";\nconst apiKey = \"ebgw8cwygvgc7vptgxty2ekq\";\n\n// This ends up in:\n// - Your git repository (forever)\n// - Your build artifacts\n// - Your error logs\n// - Anyone who can access your code\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✅ Do this instead:"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Node.js:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// Use environment variables\nconst accessToken = process.env.SPRINKLR_ACCESS_TOKEN;\nconst apiKey = process.env.SPRINKLR_API_KEY;\n\nif (!accessToken || !apiKey) {\n  throw new Error('Missing required credentials. Set SPRINKLR_ACCESS_TOKEN and SPRINKLR_API_KEY environment variables.');\n}\n\n// Or use a secrets manager (production)\nconst AWS = require('aws-sdk');\nconst secretsManager = new AWS.SecretsManager();\n\nasync function getCredentials() {\n  const secret = await secretsManager.getSecretValue({\n    SecretId: 'sprinklr/api/credentials'\n  }).promise();\n\n  return JSON.parse(secret.SecretString);\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Python:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import os\nfrom typing import Dict\n\n# Use environment variables\naccess_token = os.environ.get('SPRINKLR_ACCESS_TOKEN')\napi_key = os.environ.get('SPRINKLR_API_KEY')\n\nif not access_token or not api_key:\n    raise ValueError('Missing required credentials')\n\n# Or use a secrets manager\nimport boto3\n\ndef get_credentials() -> Dict[str, str]:\n    client = boto3.client('secretsmanager')\n    secret = client.get_secret_value(SecretId='sprinklr/api/credentials')\n    return json.loads(secret['SecretString'])\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["PHP:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"php","header":{"controls":{"copy":{}}},"source":"<?php\n// Use environment variables\n$accessToken = getenv('SPRINKLR_ACCESS_TOKEN');\n$apiKey = getenv('SPRINKLR_API_KEY');\n\nif (!$accessToken || !$apiKey) {\n    throw new Exception('Missing required credentials');\n}\n\n// Or use a secrets manager\nuse Aws\\SecretsManager\\SecretsManagerClient;\n\nfunction getCredentials() {\n    $client = new SecretsManagerClient([\n        'region' => 'us-east-1',\n        'version' => 'latest'\n    ]);\n\n    $result = $client->getSecretValue([\n        'SecretId' => 'sprinklr/api/credentials'\n    ]);\n\n    return json_decode($result['SecretString'], true);\n}\n?>\n","lang":"php"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Java:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;\nimport software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;\n\npublic class CredentialManager {\n    public static Credentials getCredentials() {\n        // Use environment variables\n        String accessToken = System.getenv(\"SPRINKLR_ACCESS_TOKEN\");\n        String apiKey = System.getenv(\"SPRINKLR_API_KEY\");\n\n        if (accessToken == null || apiKey == null) {\n            throw new IllegalStateException(\"Missing required credentials\");\n        }\n\n        return new Credentials(accessToken, apiKey);\n    }\n\n    // Or use secrets manager\n    public static Credentials getCredentialsFromSecretsManager() {\n        SecretsManagerClient client = SecretsManagerClient.create();\n\n        GetSecretValueRequest request = GetSecretValueRequest.builder()\n            .secretId(\"sprinklr/api/credentials\")\n            .build();\n\n        String secret = client.getSecretValue(request).secretString();\n        return parseCredentials(secret);\n    }\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"separate-credentials-by-environment","__idx":4},"children":["Separate credentials by environment"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use different credentials for development, staging, and production:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// config.js\nmodule.exports = {\n  development: {\n    apiUrl: 'https://qa-api-v3.sprinklr.com',\n    accessToken: process.env.DEV_SPRINKLR_ACCESS_TOKEN,\n    apiKey: process.env.DEV_SPRINKLR_API_KEY\n  },\n  production: {\n    apiUrl: 'https://api-v3.sprinklr.com',\n    accessToken: process.env.PROD_SPRINKLR_ACCESS_TOKEN,\n    apiKey: process.env.PROD_SPRINKLR_API_KEY\n  }\n};\n\nconst config = module.exports[process.env.NODE_ENV || 'development'];\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Why this matters:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Development bugs don't affect production data"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can test freely without worry"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Compromised dev credentials don't expose production"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["You can revoke environment-specific credentials independently"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"rotate-credentials-regularly","__idx":5},"children":["Rotate credentials regularly"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Implement a credential rotation schedule:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class CredentialManager {\n  constructor() {\n    this.currentCredentials = null;\n    this.credentialAge = null;\n    this.rotationWarningDays = 80; // Warn before 90-day expiry\n  }\n\n  async getCredentials() {\n    if (!this.currentCredentials) {\n      this.currentCredentials = await this.loadCredentials();\n      this.credentialAge = await this.getCredentialAge();\n    }\n\n    // Check if credentials need rotation\n    if (this.credentialAge > this.rotationWarningDays) {\n      console.warn(`⚠️ Credentials are ${this.credentialAge} days old. Consider rotating soon.`);\n      this.alertOps('Credential rotation needed');\n    }\n\n    return this.currentCredentials;\n  }\n\n  async rotateCredentials(newAccessToken, newApiKey) {\n    // Implement gradual rotation with fallback\n    const oldCredentials = this.currentCredentials;\n\n    try {\n      // Test new credentials\n      await this.testCredentials(newAccessToken, newApiKey);\n\n      // Switch to new credentials\n      this.currentCredentials = {\n        accessToken: newAccessToken,\n        apiKey: newApiKey\n      };\n\n      // Keep old credentials for 24 hours as fallback\n      setTimeout(() => {\n        console.log('Old credentials expired from fallback');\n      }, 86400000);\n\n      console.log('✅ Credentials rotated successfully');\n    } catch (error) {\n      console.error('Credential rotation failed, keeping old credentials');\n      throw error;\n    }\n  }\n\n  async testCredentials(accessToken, apiKey) {\n    // Make a test API call to verify credentials work\n    const response = await fetch('https://api-v3.sprinklr.com/api/v3/account', {\n      headers: {\n        'Authorization': `Bearer ${accessToken}`,\n        'Key': apiKey\n      }\n    });\n\n    if (!response.ok) {\n      throw new Error('Invalid credentials');\n    }\n  }\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"always-use-https","__idx":6},"children":["Always use HTTPS"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Never"]}," make API calls over plain HTTP:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// ✅ Correct - HTTPS\nconst apiUrl = 'https://api-v3.sprinklr.com';\n\n// ❌ Wrong - Plain HTTP (credentials visible in plain text!)\nconst apiUrl = 'http://api-v3.sprinklr.com';\n\n// ✅ Enforce HTTPS in code\nif (!apiUrl.startsWith('https://')) {\n  throw new Error('API URL must use HTTPS');\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"efficiency-do-more-with-less","__idx":7},"children":["Efficiency: Do more with less"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Efficient integrations use fewer API requests, process faster, and stay well under rate limits."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"use-batch-operations-religiously","__idx":8},"children":["Use batch operations religiously"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is the single most effective optimization you can make."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Impact comparison:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Task: Fetch 100 accounts\n\nWithout batching:\n- 100 API requests\n- ~30 seconds total time\n- 100 of your rate limit quota\n- More error opportunities\n\nWith batching:\n- 2 API requests (2 batches of 50)\n- ~600ms total time\n- 2 of your rate limit quota\n- 50x more efficient\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Implementation:"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Node.js:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// ❌ Inefficient\nasync function getAccountsOneByOne(accountIds) {\n  const accounts = [];\n  for (const id of accountIds) {\n    const account = await fetch(\n      `https://api-v3.sprinklr.com/api/v3/account?accountId=${id}`,\n      { headers: { 'Authorization': `Bearer ${token}`, 'Key': key } }\n    );\n    accounts.push(await account.json());\n  }\n  return accounts;\n}\n\n// ✅ Efficient\nasync function getAccountsInBatches(accountIds) {\n  const batchSize = 50;\n  const batches = [];\n\n  // Split into batches of 50\n  for (let i = 0; i < accountIds.length; i += batchSize) {\n    batches.push(accountIds.slice(i, i + batchSize));\n  }\n\n  // Fetch all batches\n  const results = await Promise.all(\n    batches.map(batch =>\n      fetch('https://api-v3.sprinklr.com/api/v3/account/batch', {\n        method: 'POST',\n        headers: {\n          'Authorization': `Bearer ${token}`,\n          'Key': key,\n          'Content-Type': 'application/json'\n        },\n        body: JSON.stringify({ accountIds: batch })\n      }).then(r => r.json())\n    )\n  );\n\n  // Flatten results\n  return results.flatMap(r => r.data);\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"cache-intelligently","__idx":9},"children":["Cache intelligently"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Not all data needs to be fresh every time."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Caching strategy by data type:"]}]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Data Type"},"children":["Data Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Recommended TTL"},"children":["Recommended TTL"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Rationale"},"children":["Rationale"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Account metadata"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["30-60 minutes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Changes infrequently"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["User profiles"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["15-30 minutes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["May update but not constantly"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Messages/posts"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1-5 minutes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["More dynamic"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cases"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1-2 minutes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Changes as agents work"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Configuration"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1-24 hours"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Rarely changes"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Production-ready cache implementation:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class SmartCache {\n  constructor() {\n    this.cache = new Map();\n    this.stats = { hits: 0, misses: 0, evictions: 0 };\n  }\n\n  set(key, value, ttlMs = 300000) {\n    // Limit cache size\n    if (this.cache.size > 10000) {\n      // Evict oldest entry\n      const firstKey = this.cache.keys().next().value;\n      this.cache.delete(firstKey);\n      this.stats.evictions++;\n    }\n\n    this.cache.set(key, {\n      value,\n      expiresAt: Date.now() + ttlMs,\n      createdAt: Date.now()\n    });\n  }\n\n  get(key) {\n    const entry = this.cache.get(key);\n\n    if (!entry) {\n      this.stats.misses++;\n      return null;\n    }\n\n    // Check expiry\n    if (Date.now() > entry.expiresAt) {\n      this.cache.delete(key);\n      this.stats.misses++;\n      return null;\n    }\n\n    this.stats.hits++;\n    return entry.value;\n  }\n\n  getStats() {\n    const total = this.stats.hits + this.stats.misses;\n    const hitRate = total > 0 ? (this.stats.hits / total * 100).toFixed(2) : 0;\n\n    return {\n      ...this.stats,\n      total,\n      hitRate: `${hitRate}%`,\n      size: this.cache.size\n    };\n  }\n\n  clear() {\n    this.cache.clear();\n  }\n\n  // Cache with automatic revalidation\n  async getOrFetch(key, fetchFn, ttl = 300000) {\n    const cached = this.get(key);\n    if (cached !== null) {\n      return cached;\n    }\n\n    const value = await fetchFn();\n    this.set(key, value, ttl);\n    return value;\n  }\n}\n\n// Usage\nconst cache = new SmartCache();\n\nasync function getAccount(accountId) {\n  return cache.getOrFetch(\n    `account:${accountId}`,\n    async () => {\n      const response = await fetch(`https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`, {\n        headers: {\n          'Authorization': `Bearer ${accessToken}`,\n          'Key': apiKey\n        }\n      });\n      return response.json();\n    },\n    1800000 // 30 minutes\n  );\n}\n\n// Monitor cache performance\nsetInterval(() => {\n  console.log('Cache stats:', cache.getStats());\n  // Output: { hits: 847, misses: 153, evictions: 2, total: 1000, hitRate: '84.70%', size: 892 }\n}, 60000);\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"implement-request-queuing","__idx":10},"children":["Implement request queuing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Prevent overwhelming the API with concurrent requests:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class RequestQueue {\n  constructor(options = {}) {\n    this.concurrency = options.concurrency || 5;\n    this.requestsPerMinute = options.requestsPerMinute || 60;\n    this.queue = [];\n    this.running = 0;\n    this.requestTimes = [];\n  }\n\n  async add(fn) {\n    return new Promise((resolve, reject) => {\n      this.queue.push({ fn, resolve, reject });\n      this.process();\n    });\n  }\n\n  async process() {\n    // Check concurrency limit\n    if (this.running >= this.concurrency) {\n      return;\n    }\n\n    // Check rate limit\n    const now = Date.now();\n    this.requestTimes = this.requestTimes.filter(t => t > now - 60000);\n\n    if (this.requestTimes.length >= this.requestsPerMinute) {\n      // Wait until oldest request is outside the 1-minute window\n      const oldestRequest = this.requestTimes[0];\n      const waitTime = 60000 - (now - oldestRequest);\n      setTimeout(() => this.process(), waitTime);\n      return;\n    }\n\n    // Get next request\n    const item = this.queue.shift();\n    if (!item) return;\n\n    this.running++;\n    this.requestTimes.push(now);\n\n    try {\n      const result = await item.fn();\n      item.resolve(result);\n    } catch (error) {\n      item.reject(error);\n    } finally {\n      this.running--;\n      // Process next request\n      setImmediate(() => this.process());\n    }\n  }\n\n  getQueueSize() {\n    return this.queue.length;\n  }\n}\n\n// Usage\nconst queue = new RequestQueue({\n  concurrency: 5,           // Max 5 concurrent requests\n  requestsPerMinute: 60     // Stay under rate limit\n});\n\n// All requests automatically queued and rate-limited\nasync function fetchAccount(accountId) {\n  return queue.add(async () => {\n    const response = await fetch(\n      `https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`,\n      {\n        headers: {\n          'Authorization': `Bearer ${accessToken}`,\n          'Key': apiKey\n        }\n      }\n    );\n    return response.json();\n  });\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"error-handling-fail-gracefully","__idx":11},"children":["Error handling: Fail gracefully"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Production systems fail. The question is whether your integration fails gracefully or catastrophically."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"implement-comprehensive-retry-logic","__idx":12},"children":["Implement comprehensive retry logic"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"async function retryWithBackoff(fn, options = {}) {\n  const {\n    maxRetries = 5,\n    baseDelay = 1000,\n    maxDelay = 30000,\n    retryableErrors = [408, 429, 500, 502, 503, 504]\n  } = options;\n\n  let lastError;\n\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      return await fn();\n    } catch (error) {\n      lastError = error;\n\n      // Check if error is retryable\n      const statusCode = error.response?.status;\n      if (!retryableErrors.includes(statusCode)) {\n        throw error; // Don't retry non-retryable errors\n      }\n\n      // Don't retry on last attempt\n      if (attempt === maxRetries - 1) {\n        break;\n      }\n\n      // Calculate delay with exponential backoff + jitter\n      const exponentialDelay = Math.min(\n        baseDelay * Math.pow(2, attempt),\n        maxDelay\n      );\n      const jitter = Math.random() * 1000; // Add randomness\n      const delay = exponentialDelay + jitter;\n\n      console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);\n      await new Promise(resolve => setTimeout(resolve, delay));\n    }\n  }\n\n  throw lastError;\n}\n\n// Usage\nconst account = await retryWithBackoff(\n  async () => {\n    const response = await fetch(\n      `https://api-v3.sprinklr.com/api/v3/account?accountId=${accountId}`,\n      {\n        headers: {\n          'Authorization': `Bearer ${accessToken}`,\n          'Key': apiKey\n        }\n      }\n    );\n\n    if (!response.ok) {\n      const error = new Error(`HTTP ${response.status}`);\n      error.response = response;\n      throw error;\n    }\n\n    return response.json();\n  },\n  {\n    maxRetries: 5,\n    retryableErrors: [429, 500, 502, 503, 504]\n  }\n);\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"log-errors-comprehensively","__idx":13},"children":["Log errors comprehensively"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class ErrorLogger {\n  static log(error, context = {}) {\n    const errorInfo = {\n      timestamp: new Date().toISOString(),\n      message: error.message,\n      stack: error.stack,\n      ...context\n    };\n\n    // Log different details based on error type\n    if (error.response) {\n      errorInfo.status = error.response.status;\n      errorInfo.statusText = error.response.statusText;\n      errorInfo.headers = Object.fromEntries(error.response.headers);\n    }\n\n    // Log to console (development)\n    console.error('API Error:', JSON.stringify(errorInfo, null, 2));\n\n    // Send to error tracking service (production)\n    if (process.env.NODE_ENV === 'production') {\n      this.sendToErrorTracker(errorInfo);\n    }\n\n    return errorInfo;\n  }\n\n  static sendToErrorTracker(errorInfo) {\n    // Send to Sentry, Datadog, CloudWatch, etc.\n    // Example with hypothetical service:\n    errorTracker.captureException(errorInfo);\n  }\n}\n\n// Usage\ntry {\n  const response = await makeAPICall();\n} catch (error) {\n  ErrorLogger.log(error, {\n    operation: 'fetchAccount',\n    accountId: '600057112',\n    userId: currentUser.id,\n    environment: process.env.NODE_ENV\n  });\n\n  // Rethrow or handle\n  throw error;\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"provide-user-friendly-error-messages","__idx":14},"children":["Provide user-friendly error messages"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Never expose raw API errors to end users:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class UserFriendlyError extends Error {\n  constructor(apiError, userMessage) {\n    super(userMessage);\n    this.apiError = apiError;\n    this.userMessage = userMessage;\n  }\n\n  static fromAPIError(apiError, context = {}) {\n    const status = apiError.response?.status;\n    const code = apiError.code;\n\n    let userMessage;\n\n    switch (code) {\n      case 'INVALID_PARAMETER':\n        userMessage = 'The information provided is invalid. Please check and try again.';\n        break;\n      case 'NOT_FOUND':\n        userMessage = `The ${context.resourceType || 'item'} you're looking for doesn't exist.`;\n        break;\n      case 'UNAUTHORIZED':\n        userMessage = 'Your session has expired. Please log in again.';\n        break;\n      case 'FORBIDDEN':\n        userMessage = \"You don't have permission to access this resource.\";\n        break;\n      case 'RATE_LIMIT_EXCEEDED':\n        userMessage = 'Too many requests. Please wait a moment and try again.';\n        break;\n      case 'VALIDATION_ERROR':\n        userMessage = 'Some required information is missing or incorrect.';\n        break;\n      default:\n        if (status >= 500) {\n          userMessage = 'Our service is temporarily unavailable. Please try again in a few minutes.';\n        } else {\n          userMessage = 'Something went wrong. Please try again.';\n        }\n    }\n\n    return new UserFriendlyError(apiError, userMessage);\n  }\n}\n\n// Usage in your application\ntry {\n  const account = await getAccount(accountId);\n} catch (error) {\n  const friendlyError = UserFriendlyError.fromAPIError(error, {\n    resourceType: 'account'\n  });\n\n  // Show to user\n  showErrorToUser(friendlyError.userMessage);\n\n  // Log technical details\n  console.error(friendlyError.apiError);\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"rate-limiting-stay-under-the-radar","__idx":15},"children":["Rate limiting: Stay under the radar"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respecting rate limits isn't just polite—it's essential for a reliable integration."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"monitor-your-usage-proactively","__idx":16},"children":["Monitor your usage proactively"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class RateLimitMonitor {\n  constructor(alertThreshold = 0.8) {\n    this.alertThreshold = alertThreshold;\n    this.requests = [];\n  }\n\n  logRequest(response) {\n    const limit = parseInt(response.headers.get('X-RateLimit-Limit'));\n    const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));\n    const reset = parseInt(response.headers.get('X-RateLimit-Reset'));\n\n    this.requests.push({\n      timestamp: Date.now(),\n      limit,\n      remaining,\n      reset\n    });\n\n    // Alert if approaching limit\n    const usagePercentage = 1 - (remaining / limit);\n    if (usagePercentage > this.alertThreshold) {\n      this.alert({\n        message: `Rate limit warning: ${Math.round(usagePercentage * 100)}% used`,\n        remaining,\n        limit,\n        resetAt: new Date(reset * 1000)\n      });\n    }\n\n    return { limit, remaining, reset };\n  }\n\n  alert(info) {\n    console.warn('⚠️ RATE LIMIT WARNING:', info);\n    // Send to monitoring system\n    if (process.env.NODE_ENV === 'production') {\n      this.notifyOpsTeam(info);\n    }\n  }\n\n  getUsageStats() {\n    const now = Date.now();\n    const lastMinute = this.requests.filter(r => r.timestamp > now - 60000);\n    const lastHour = this.requests.filter(r => r.timestamp > now - 3600000);\n\n    return {\n      lastMinute: {\n        count: lastMinute.length,\n        avgRemaining: lastMinute.reduce((sum, r) => sum + r.remaining, 0) / lastMinute.length\n      },\n      lastHour: {\n        count: lastHour.length\n      }\n    };\n  }\n}\n\n// Usage\nconst monitor = new RateLimitMonitor(0.8); // Alert at 80% usage\n\nasync function makeAPICall(endpoint) {\n  const response = await fetch(endpoint, {\n    headers: {\n      'Authorization': `Bearer ${accessToken}`,\n      'Key': apiKey\n    }\n  });\n\n  monitor.logRequest(response);\n\n  return response.json();\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"validation-trust-nothing","__idx":17},"children":["Validation: Trust nothing"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validate both inputs and outputs to catch issues early."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-requests-before-sending","__idx":18},"children":["Validate requests before sending"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class RequestValidator {\n  static validateAccountId(accountId) {\n    if (!accountId) {\n      throw new ValidationError('Account ID is required');\n    }\n\n    if (typeof accountId !== 'string') {\n      throw new ValidationError('Account ID must be a string');\n    }\n\n    if (accountId.length < 1 || accountId.length > 100) {\n      throw new ValidationError('Account ID must be 1-100 characters');\n    }\n\n    return true;\n  }\n\n  static validateSearchParams(params) {\n    if (params.page !== undefined) {\n      if (!Number.isInteger(params.page) || params.page < 0) {\n        throw new ValidationError('Page must be a non-negative integer');\n      }\n    }\n\n    if (params.size !== undefined) {\n      if (!Number.isInteger(params.size) || params.size < 1 || params.size > 100) {\n        throw new ValidationError('Size must be between 1 and 100');\n      }\n    }\n\n    if (params.startDate && params.endDate) {\n      const start = new Date(params.startDate);\n      const end = new Date(params.endDate);\n\n      if (end < start) {\n        throw new ValidationError('End date must be after start date');\n      }\n    }\n\n    return true;\n  }\n}\n\n// Usage\nasync function searchCases(params) {\n  RequestValidator.validateSearchParams(params);\n\n  const response = await fetch('https://api-v3.sprinklr.com/api/v3/case/search', {\n    method: 'POST',\n    headers: {\n      'Authorization': `Bearer ${accessToken}`,\n      'Key': apiKey,\n      'Content-Type': 'application/json'\n    },\n    body: JSON.stringify(params)\n  });\n\n  return response.json();\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"validate-responses-before-using","__idx":19},"children":["Validate responses before using"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class ResponseValidator {\n  static validate(response, expectedSchema) {\n    if (!response || typeof response !== 'object') {\n      throw new ValidationError('Invalid response format');\n    }\n\n    if (response.success === false) {\n      if (!response.errors || !Array.isArray(response.errors)) {\n        throw new ValidationError('Error response missing errors array');\n      }\n      // This is a valid error response\n      return response;\n    }\n\n    if (response.success !== true) {\n      throw new ValidationError('Response missing success field');\n    }\n\n    if (!response.data) {\n      throw new ValidationError('Success response missing data field');\n    }\n\n    // Validate against expected schema\n    if (expectedSchema) {\n      this.validateSchema(response.data, expectedSchema);\n    }\n\n    return response;\n  }\n\n  static validateSchema(data, schema) {\n    for (const [field, type] of Object.entries(schema)) {\n      if (Array.isArray(data)) {\n        // Validate each item in array\n        data.forEach(item => {\n          if (typeof item[field] !== type && item[field] !== null) {\n            console.warn(`Field ${field} has unexpected type. Expected ${type}, got ${typeof item[field]}`);\n          }\n        });\n      } else {\n        if (typeof data[field] !== type && data[field] !== null) {\n          console.warn(`Field ${field} has unexpected type. Expected ${type}, got ${typeof data[field]}`);\n        }\n      }\n    }\n  }\n}\n\n// Usage\nconst response = await fetch('https://api-v3.sprinklr.com/api/v3/account', {\n  headers: {\n    'Authorization': `Bearer ${accessToken}`,\n    'Key': apiKey\n  }\n});\n\nconst data = await response.json();\n\n// Validate response structure\nResponseValidator.validate(data, {\n  accountId: 'string',\n  name: 'string',\n  channelType: 'string',\n  status: 'string'\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"testing-test-like-you-mean-it","__idx":20},"children":["Testing: Test like you mean it"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Comprehensive testing catches issues before users do."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"test-error-scenarios-explicitly","__idx":21},"children":["Test error scenarios explicitly"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"describe('Sprinklr API Client', () => {\n  test('handles 404 errors gracefully', async () => {\n    const client = new SprinklrClient(token, key);\n\n    await expect(\n      client.getAccount('nonexistent-id')\n    ).rejects.toThrow('not found');\n  });\n\n  test('retries on 429 rate limit', async () => {\n    const client = new SprinklrClient(token, key);\n\n    // Mock API to return 429 then 200\n    mockAPI\n      .onGet('/account').replyOnce(429)\n      .onGet('/account').replyOnce(200, { success: true, data: {} });\n\n    const result = await client.getAccount('123');\n    expect(result).toBeDefined();\n  });\n\n  test('handles network timeouts', async () => {\n    const client = new SprinklrClient(token, key, { timeout: 100 });\n\n    mockAPI.onGet('/account').timeout();\n\n    await expect(\n      client.getAccount('123')\n    ).rejects.toThrow('timeout');\n  });\n\n  test('validates response structure', async () => {\n    const client = new SprinklrClient(token, key);\n\n    mockAPI.onGet('/account').reply(200, { invalid: 'response' });\n\n    await expect(\n      client.getAccount('123')\n    ).rejects.toThrow('Invalid response');\n  });\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"use-qa-environment-for-integration-tests","__idx":22},"children":["Use QA environment for integration tests"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"// config/test.js\nmodule.exports = {\n  apiUrl: process.env.QA_API_URL || 'https://qa-api-v3.sprinklr.com',\n  accessToken: process.env.QA_ACCESS_TOKEN,\n  apiKey: process.env.QA_API_KEY\n};\n\n// tests/integration.test.js\ndescribe('Integration Tests (QA Environment)', () => {\n  const client = new SprinklrClient(\n    process.env.QA_ACCESS_TOKEN,\n    process.env.QA_API_KEY\n  );\n\n  test('can fetch accounts from QA', async () => {\n    const accounts = await client.getAccounts();\n    expect(accounts).toBeDefined();\n    expect(Array.isArray(accounts.data)).toBe(true);\n  });\n\n  test('pagination works correctly', async () => {\n    const page1 = await client.searchCases({ page: 0, size: 10 });\n    const page2 = await client.searchCases({ page: 1, size: 10 });\n\n    expect(page1.pagination.page).toBe(0);\n    expect(page2.pagination.page).toBe(1);\n    expect(page1.data).not.toEqual(page2.data);\n  });\n});\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"performance-make-it-fast","__idx":23},"children":["Performance: Make it fast"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Slow integrations frustrate users and waste resources."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"use-appropriate-timeouts","__idx":24},"children":["Use appropriate timeouts"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class SprinklrClient {\n  constructor(accessToken, apiKey, options = {}) {\n    this.accessToken = accessToken;\n    this.apiKey = apiKey;\n    this.timeout = options.timeout || 30000; // 30 seconds default\n  }\n\n  async request(endpoint, options = {}) {\n    const controller = new AbortController();\n    const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n    try {\n      const response = await fetch(endpoint, {\n        ...options,\n        signal: controller.signal,\n        headers: {\n          'Authorization': `Bearer ${this.accessToken}`,\n          'Key': this.apiKey,\n          ...options.headers\n        }\n      });\n\n      clearTimeout(timeoutId);\n      return response;\n    } catch (error) {\n      clearTimeout(timeoutId);\n      if (error.name === 'AbortError') {\n        throw new Error(`Request timeout after ${this.timeout}ms`);\n      }\n      throw error;\n    }\n  }\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"use-connection-pooling","__idx":25},"children":["Use connection pooling"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"const https = require('https');\nconst fetch = require('node-fetch');\n\n// Create agent with connection pooling\nconst agent = new https.Agent({\n  keepAlive: true,\n  keepAliveMsecs: 30000,\n  maxSockets: 50,\n  maxFreeSockets: 10\n});\n\n// Use agent for all requests\nasync function makeRequest(url) {\n  return fetch(url, {\n    agent,\n    headers: {\n      'Authorization': `Bearer ${accessToken}`,\n      'Key': apiKey\n    }\n  });\n}\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"monitoring-know-whats-happening","__idx":26},"children":["Monitoring: Know what's happening"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can't fix what you can't see."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"track-key-metrics","__idx":27},"children":["Track key metrics"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"class MetricsCollector {\n  constructor() {\n    this.metrics = {\n      requests: {\n        total: 0,\n        successful: 0,\n        failed: 0\n      },\n      latency: {\n        total: 0,\n        measurements: []\n      },\n      errors: new Map(),\n      endpoints: new Map()\n    };\n  }\n\n  recordRequest(endpoint, latency, success, error = null) {\n    // Track request counts\n    this.metrics.requests.total++;\n    if (success) {\n      this.metrics.requests.successful++;\n    } else {\n      this.metrics.requests.failed++;\n    }\n\n    // Track latency\n    this.metrics.latency.total += latency;\n    this.metrics.latency.measurements.push(latency);\n\n    // Keep only last 1000 measurements\n    if (this.metrics.latency.measurements.length > 1000) {\n      this.metrics.latency.measurements.shift();\n    }\n\n    // Track errors\n    if (error) {\n      const errorKey = `${error.code || error.message}`;\n      this.metrics.errors.set(\n        errorKey,\n        (this.metrics.errors.get(errorKey) || 0) + 1\n      );\n    }\n\n    // Track per-endpoint metrics\n    if (!this.metrics.endpoints.has(endpoint)) {\n      this.metrics.endpoints.set(endpoint, {\n        requests: 0,\n        failures: 0,\n        totalLatency: 0\n      });\n    }\n\n    const endpointMetrics = this.metrics.endpoints.get(endpoint);\n    endpointMetrics.requests++;\n    endpointMetrics.totalLatency += latency;\n    if (!success) endpointMetrics.failures++;\n  }\n\n  getReport() {\n    const { requests, latency, errors, endpoints } = this.metrics;\n\n    return {\n      requests: {\n        ...requests,\n        errorRate: ((requests.failed / requests.total) * 100).toFixed(2) + '%'\n      },\n      latency: {\n        avg: (latency.total / requests.total).toFixed(2) + 'ms',\n        p50: this.getPercentile(latency.measurements, 0.5) + 'ms',\n        p95: this.getPercentile(latency.measurements, 0.95) + 'ms',\n        p99: this.getPercentile(latency.measurements, 0.99) + 'ms'\n      },\n      topErrors: Array.from(errors.entries())\n        .sort((a, b) => b[1] - a[1])\n        .slice(0, 5)\n        .map(([error, count]) => ({ error, count })),\n      topEndpoints: Array.from(endpoints.entries())\n        .map(([endpoint, metrics]) => ({\n          endpoint,\n          requests: metrics.requests,\n          avgLatency: (metrics.totalLatency / metrics.requests).toFixed(2) + 'ms',\n          errorRate: ((metrics.failures / metrics.requests) * 100).toFixed(2) + '%'\n        }))\n        .sort((a, b) => b.requests - a.requests)\n        .slice(0, 5)\n    };\n  }\n\n  getPercentile(arr, percentile) {\n    if (arr.length === 0) return 0;\n    const sorted = arr.slice().sort((a, b) => a - b);\n    const index = Math.floor(sorted.length * percentile);\n    return sorted[index];\n  }\n}\n\n// Usage\nconst metrics = new MetricsCollector();\n\nasync function makeAPICall(endpoint) {\n  const startTime = Date.now();\n  let success = false;\n  let error = null;\n\n  try {\n    const response = await fetch(endpoint, {\n      headers: {\n        'Authorization': `Bearer ${accessToken}`,\n        'Key': apiKey\n      }\n    });\n\n    success = response.ok;\n    if (!success) {\n      error = await response.json();\n    }\n\n    return response.json();\n  } catch (err) {\n    error = err;\n    throw err;\n  } finally {\n    const latency = Date.now() - startTime;\n    metrics.recordRequest(endpoint, latency, success, error);\n  }\n}\n\n// Generate hourly reports\nsetInterval(() => {\n  console.log('=== API Metrics Report ===');\n  console.log(JSON.stringify(metrics.getReport(), null, 2));\n}, 3600000); // Every hour\n","lang":"javascript"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"production-checklist","__idx":28},"children":["Production checklist"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Before deploying to production, verify these items:"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"security-","__idx":29},"children":["Security ✓"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Credentials stored in secrets manager (not in code)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Different credentials for each environment"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," HTTPS enforced for all requests"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," API keys not logged or exposed in error messages"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Credential rotation schedule implemented"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"reliability-","__idx":30},"children":["Reliability ✓"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Exponential backoff retry logic implemented"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Circuit breakers for cascading failures"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Timeouts configured (30s for API calls)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Error handling covers all scenarios"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Graceful degradation when API is unavailable"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"performance-","__idx":31},"children":["Performance ✓"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Batch operations used wherever possible"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Caching implemented with appropriate TTLs"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Connection pooling enabled"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Request queuing to prevent overwhelming API"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Pagination implemented for large datasets"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"monitoring-","__idx":32},"children":["Monitoring ✓"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Request/error metrics tracked"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Rate limit usage monitored"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Alerts configured for anomalies"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Logs centralized and searchable"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Performance metrics tracked (p50, p95, p99 latency)"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"testing-","__idx":33},"children":["Testing ✓"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Unit tests cover happy paths"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Integration tests cover error scenarios"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Load testing performed"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," QA environment tested thoroughly"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"input","attributes":{"checked":false,"type":"checkbox","readOnly":true},"children":[]}," Failure scenarios tested (network issues, timeouts, rate limits)"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"next-steps","__idx":34},"children":["Next steps"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/error-handling"},"children":["Error Handling"]}]}," - Deep dive into error handling strategies"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/rate-limiting"},"children":["Rate Limiting"]}]}," - Understand and optimize for rate limits"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/pagination"},"children":["Pagination"]}]}," - Handle large datasets efficiently"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/webhooks"},"children":["Webhooks"]}]}," - Real-time event notifications"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/guides/authentication"},"children":["Authentication"]}]}," - Secure credential management"]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"need-help","__idx":35},"children":["Need help?"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you're building a production integration:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Review this checklist"]}," - Ensure you've implemented all critical patterns"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Test in QA first"]}," - Never deploy untested code to production"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Monitor from day one"]}," - Set up metrics and alerts before launch"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Start small"]}," - Begin with conservative rate limits and scale up"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Support resources:"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Technical support: ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://support.sprinklr.com"},"children":["support.sprinklr.com"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["API documentation: This guide and related docs"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Community forums: Share patterns with other developers"]}]}]},"headings":[{"value":"Best Practices","id":"best-practices","depth":1},{"value":"Why best practices matter","id":"why-best-practices-matter","depth":2},{"value":"Authentication & security","id":"authentication--security","depth":2},{"value":"Store credentials securely","id":"store-credentials-securely","depth":3},{"value":"Separate credentials by environment","id":"separate-credentials-by-environment","depth":3},{"value":"Rotate credentials regularly","id":"rotate-credentials-regularly","depth":3},{"value":"Always use HTTPS","id":"always-use-https","depth":3},{"value":"Efficiency: Do more with less","id":"efficiency-do-more-with-less","depth":2},{"value":"Use batch operations religiously","id":"use-batch-operations-religiously","depth":3},{"value":"Cache intelligently","id":"cache-intelligently","depth":3},{"value":"Implement request queuing","id":"implement-request-queuing","depth":3},{"value":"Error handling: Fail gracefully","id":"error-handling-fail-gracefully","depth":2},{"value":"Implement comprehensive retry logic","id":"implement-comprehensive-retry-logic","depth":3},{"value":"Log errors comprehensively","id":"log-errors-comprehensively","depth":3},{"value":"Provide user-friendly error messages","id":"provide-user-friendly-error-messages","depth":3},{"value":"Rate limiting: Stay under the radar","id":"rate-limiting-stay-under-the-radar","depth":2},{"value":"Monitor your usage proactively","id":"monitor-your-usage-proactively","depth":3},{"value":"Validation: Trust nothing","id":"validation-trust-nothing","depth":2},{"value":"Validate requests before sending","id":"validate-requests-before-sending","depth":3},{"value":"Validate responses before using","id":"validate-responses-before-using","depth":3},{"value":"Testing: Test like you mean it","id":"testing-test-like-you-mean-it","depth":2},{"value":"Test error scenarios explicitly","id":"test-error-scenarios-explicitly","depth":3},{"value":"Use QA environment for integration tests","id":"use-qa-environment-for-integration-tests","depth":3},{"value":"Performance: Make it fast","id":"performance-make-it-fast","depth":2},{"value":"Use appropriate timeouts","id":"use-appropriate-timeouts","depth":3},{"value":"Use connection pooling","id":"use-connection-pooling","depth":3},{"value":"Monitoring: Know what's happening","id":"monitoring-know-whats-happening","depth":2},{"value":"Track key metrics","id":"track-key-metrics","depth":3},{"value":"Production checklist","id":"production-checklist","depth":2},{"value":"Security ✓","id":"security-","depth":3},{"value":"Reliability ✓","id":"reliability-","depth":3},{"value":"Performance ✓","id":"performance-","depth":3},{"value":"Monitoring ✓","id":"monitoring-","depth":3},{"value":"Testing ✓","id":"testing-","depth":3},{"value":"Next steps","id":"next-steps","depth":2},{"value":"Need help?","id":"need-help","depth":2}],"frontmatter":{"seo":{"title":"Best Practices"}},"lastModified":"2026-10-27T09:16:56.926Z","pagePropGetterError":{"message":"","name":""}},"slug":"/guides/best-practices","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}