API-Referenz

Endpunkte, Datenmodelle und Fehlercodes der LiSA REST API, erzeugt aus dem offiziellen API-Wiki.

Wiki-Stand e01e79e · 19.8.2026

Best Practices

Comprehensive guide to best practices for using the LISA REST API effectively and securely.

Authentication Best Practices#

Token Management#

Secure Token Storage#

// Server-side: Use environment variables
const integratorUID = process.env.LISA_INTEGRATOR_UID;
const accessToken = process.env.LISA_ACCESS_TOKEN;

// Client-side: Use secure storage
const accessToken = await secureStorage.getItem('lisa_access_token');
const refreshToken = await secureStorage.getItem('lisa_refresh_token');

// Never store tokens in localStorage for production
// localStorage.setItem('token', token); // DON'T DO THIS

Token Lifecycle Management#

class TokenManager {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
    this.tokenExpiry = null;
  }

  isTokenExpired() {
    if (!this.tokenExpiry) return true;
    return Date.now() >= this.tokenExpiry;
  }

  async ensureValidToken() {
    if (!this.accessToken || this.isTokenExpired()) {
      if (this.refreshToken && !this.isRefreshTokenExpired()) {
        await this.refreshAccessToken();
      } else {
        await this.generateNewTokens();
      }
    }
  }

  async refreshAccessToken() {
    try {
      const response = await fetch('/api/v1/tokens/refresh', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ refreshToken: this.refreshToken })
      });
      
      const data = await response.json();
      if (data.success) {
        this.accessToken = data.data.accessToken;
        this.refreshToken = data.data.refreshToken;
        this.tokenExpiry = data.data.expiresAt;
      }
    } catch (error) {
      console.error('Token refresh failed:', error);
      throw error;
    }
  }
}

Request Security#

Always Use HTTPS#

// Production: Always use HTTPS
const baseURL = 'https://api.biocv.info/api/v1';

// Development: Can use HTTP for localhost
const baseURL = process.env.NODE_ENV === 'production' 
  ? 'https://api.biocv.info/api/v1'
  : 'http://localhost:3000/api/v1';

Validate Input Data#

function validateAnimalData(animalData) {
  const errors = [];
  
  if (!animalData.ID) errors.push('ID is required');
  if (!animalData.MAC) errors.push('MAC is required');
  if (!['male', 'female'].includes(animalData.Sex)) {
    errors.push('Sex must be male or female');
  }
  if (animalData.Battery < 0 || animalData.Battery > 100) {
    errors.push('Battery must be between 0 and 100');
  }
  
  return errors;
}

Performance Best Practices#

Pagination#

Implement Proper Pagination#

async function getAllAnimals(userId) {
  let page = 1;
  let allAnimals = [];
  let hasMore = true;

  while (hasMore) {
    try {
      const response = await client.getAnimals(userId, page, 100);
      allAnimals.push(...response.data);
      
      hasMore = page < response.pagination.totalPages;
      page++;
      
      // Add small delay to avoid rate limiting
      await new Promise(resolve => setTimeout(resolve, 100));
    } catch (error) {
      console.error(`Error fetching page ${page}:`, error);
      break;
    }
  }

  return allAnimals;
}

Use Appropriate Page Sizes#

// Good: Reasonable page sizes
const animals = await client.getAnimals(userId, 1, 50);

// Avoid: Very large page sizes
const animals = await client.getAnimals(userId, 1, 1000); // May timeout

// Avoid: Very small page sizes
const animals = await client.getAnimals(userId, 1, 1); // Inefficient

Caching#

Implement Response Caching#

class CachedAPIClient {
  constructor(apiClient, cacheTTL = 300000) { // 5 minutes
    this.apiClient = apiClient;
    this.cache = new Map();
    this.cacheTTL = cacheTTL;
  }

  getCacheKey(endpoint, params = {}) {
    return `${endpoint}_${JSON.stringify(params)}`;
  }

  async getCachedData(endpoint, params, fetchFunction) {
    const cacheKey = this.getCacheKey(endpoint, params);
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return cached.data;
    }

    const data = await fetchFunction();
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });

    return data;
  }

  async getAnimals(userId, page = 1, limit = 10) {
    return this.getCachedData(
      `/users/${userId}/animals`,
      { page, limit },
      () => this.apiClient.getAnimals(userId, page, limit)
    );
  }
}

Rate Limiting#

Implement Exponential Backoff#

async function makeRequestWithRetry(url, options = {}, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 429) {
        if (attempt < maxRetries) {
          const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
      }
      
      return response;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
}

Monitor Rate Limits#

function checkRateLimit(response) {
  const remaining = response.headers.get('X-RateLimit-Remaining');
  const reset = response.headers.get('X-RateLimit-Reset');
  
  if (remaining && parseInt(remaining) < 10) {
    console.warn(`Rate limit warning: ${remaining} requests remaining`);
  }
  
  return {
    remaining: parseInt(remaining) || null,
    reset: parseInt(reset) || null
  };
}

Error Handling Best Practices#

Comprehensive Error Handling#

class RobustAPIClient {
  async makeRequest(endpoint, options = {}) {
    try {
      const response = await fetch(endpoint, options);
      const data = await response.json();
      
      if (!data.success) {
        // Handle different error types
        if (response.status === 401) {
          await this.handleTokenExpiry();
          return this.makeRequest(endpoint, options); // Retry
        }
        
        if (response.status === 429) {
          await this.handleRateLimit();
          return this.makeRequest(endpoint, options); // Retry
        }
        
        throw new APIError(data.error, response.status, data.details);
      }
      
      return data;
    } catch (error) {
      if (error instanceof APIError) throw error;
      
      // Handle network errors
      if (error.name === 'TypeError' && error.message.includes('fetch')) {
        throw new NetworkError('Network request failed', error);
      }
      
      throw new APIError('Unknown error occurred', 500, null, error);
    }
  }

  async handleTokenExpiry() {
    try {
      await this.refreshToken();
    } catch (error) {
      await this.generateNewTokens();
    }
  }

  async handleRateLimit() {
    const delay = Math.random() * 2000 + 1000; // 1-3 seconds
    await new Promise(resolve => setTimeout(resolve, delay));
  }
}

class APIError extends Error {
  constructor(message, status, details, originalError = null) {
    super(message);
    this.name = 'APIError';
    this.status = status;
    this.details = details;
    this.originalError = originalError;
  }
}

Logging and Monitoring#

class MonitoredAPIClient {
  constructor(apiClient, logger) {
    this.apiClient = apiClient;
    this.logger = logger;
  }

  async makeRequest(endpoint, options = {}) {
    const startTime = Date.now();
    
    try {
      const result = await this.apiClient.makeRequest(endpoint, options);
      
      this.logger.info('API request successful', {
        endpoint,
        duration: Date.now() - startTime,
        status: 'success'
      });
      
      return result;
    } catch (error) {
      this.logger.error('API request failed', {
        endpoint,
        duration: Date.now() - startTime,
        error: error.message,
        status: 'error'
      });
      
      throw error;
    }
  }
}

Data Processing Best Practices#

Efficient Data Processing#

class DataProcessor {
  static processAnimals(animals) {
    return animals.map(animal => ({
      id: animal.ID,
      mac: animal.MAC,
      group: animal.Group,
      sex: animal.Sex,
      temperature: animal.CurrentTemperature,
      battery: animal.Battery,
      suspicious: animal.Suspicious,
      lastUpdate: new Date(animal.t)
    }));
  }

  static filterActiveAnimals(animals) {
    return animals.filter(animal => animal.Active);
  }

  static groupByProperty(animals, property) {
    return animals.reduce((groups, animal) => {
      const key = animal[property];
      if (!groups[key]) groups[key] = [];
      groups[key].push(animal);
      return groups;
    }, {});
  }

  static calculateStatistics(animals) {
    const activeAnimals = this.filterActiveAnimals(animals);
    
    return {
      total: animals.length,
      active: activeAnimals.length,
      averageTemperature: activeAnimals.reduce((sum, a) => sum + a.CurrentTemperature, 0) / activeAnimals.length,
      lowBattery: activeAnimals.filter(a => a.Battery < 20).length,
      suspicious: activeAnimals.filter(a => a.Suspicious).length
    };
  }
}

Batch Operations#

class BatchProcessor {
  constructor(apiClient, batchSize = 10) {
    this.apiClient = apiClient;
    this.batchSize = batchSize;
  }

  async processInBatches(items, processor) {
    const results = [];
    
    for (let i = 0; i < items.length; i += this.batchSize) {
      const batch = items.slice(i, i + this.batchSize);
      
      try {
        const batchResults = await Promise.all(
          batch.map(item => processor(item))
        );
        results.push(...batchResults);
        
        // Add delay between batches
        if (i + this.batchSize < items.length) {
          await new Promise(resolve => setTimeout(resolve, 100));
        }
      } catch (error) {
        console.error(`Batch ${i / this.batchSize + 1} failed:`, error);
        // Continue with next batch
      }
    }
    
    return results;
  }
}

Security Best Practices#

Input Validation#

class InputValidator {
  static validateMAC(mac) {
    const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
    return macRegex.test(mac);
  }

  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  static validateUserID(userId) {
    return typeof userId === 'string' && userId.length > 0;
  }

  static sanitizeInput(input) {
    if (typeof input === 'string') {
      return input.trim().replace(/[<>]/g, '');
    }
    return input;
  }
}

Secure Configuration#

// Use environment variables for sensitive data
const config = {
  integratorUID: process.env.LISA_INTEGRATOR_UID,
  baseURL: process.env.LISA_API_URL || 'https://api.biocv.info/api/v1',
  timeout: parseInt(process.env.LISA_API_TIMEOUT) || 30000,
  retryAttempts: parseInt(process.env.LISA_API_RETRIES) || 3
};

// Validate configuration on startup
function validateConfig() {
  const required = ['integratorUID'];
  const missing = required.filter(key => !config[key]);
  
  if (missing.length > 0) {
    throw new Error(`Missing required configuration: ${missing.join(', ')}`);
  }
}

Testing Best Practices#

Unit Testing#

// Mock API responses for testing
const mockAPIResponse = {
  success: true,
  data: [
    {
      ID: 'test-animal-1',
      MAC: 'AA:BB:CC:DD:EE:FF',
      Group: 'TestGroup',
      Sex: 'female',
      Active: true,
      CurrentTemperature: 38.5,
      Battery: 85
    }
  ],
  pagination: {
    page: 1,
    limit: 10,
    total: 1,
    totalPages: 1
  },
  timestamp: '2024-12-01T08:47:16.838Z'
};

// Test API client
describe('LISAPIClient', () => {
  let client;
  
  beforeEach(() => {
    client = new LISAPIClient('test-integrator');
    // Mock fetch
    global.fetch = jest.fn();
  });

  test('should get animals successfully', async () => {
    fetch.mockResolvedValueOnce({
      json: () => Promise.resolve(mockAPIResponse)
    });

    const result = await client.getAnimals('user123');
    
    expect(result.success).toBe(true);
    expect(result.data).toHaveLength(1);
    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining('/users/user123/animals'),
      expect.objectContaining({
        headers: expect.objectContaining({
          'Authorization': expect.stringContaining('Bearer')
        })
      })
    );
  });
});

Integration Testing#

// Test with real API (use test environment)
describe('LISA API Integration', () => {
  let client;
  
  beforeAll(async () => {
    client = new LISAPIClient(process.env.TEST_INTEGRATOR_UID);
    await client.generateTokens();
  });

  test('should authenticate successfully', async () => {
    const response = await client.makeRequest('/tokens/validate');
    expect(response.success).toBe(true);
  });

  test('should handle rate limiting gracefully', async () => {
    const promises = Array(10).fill().map(() => 
      client.makeRequest('/health')
    );
    
    const results = await Promise.allSettled(promises);
    const successful = results.filter(r => r.status === 'fulfilled');
    
    expect(successful.length).toBeGreaterThan(0);
  });
});

Monitoring and Alerting#

Health Checks#

class HealthMonitor {
  constructor(apiClient) {
    this.apiClient = apiClient;
    this.lastHealthCheck = null;
    this.healthStatus = 'unknown';
  }

  async checkHealth() {
    try {
      const startTime = Date.now();
      const response = await this.apiClient.makeRequest('/health');
      const duration = Date.now() - startTime;
      
      this.healthStatus = 'healthy';
      this.lastHealthCheck = new Date();
      
      return {
        status: 'healthy',
        duration,
        timestamp: this.lastHealthCheck
      };
    } catch (error) {
      this.healthStatus = 'unhealthy';
      this.lastHealthCheck = new Date();
      
      return {
        status: 'unhealthy',
        error: error.message,
        timestamp: this.lastHealthCheck
      };
    }
  }

  async startMonitoring(intervalMs = 60000) {
    setInterval(async () => {
      const health = await this.checkHealth();
      if (health.status === 'unhealthy') {
        console.error('API health check failed:', health);
        // Send alert to monitoring service
      }
    }, intervalMs);
  }
}

Performance Monitoring#

class PerformanceMonitor {
  constructor() {
    this.metrics = {
      requestCount: 0,
      totalDuration: 0,
      errorCount: 0,
      lastRequest: null
    };
  }

  recordRequest(duration, success) {
    this.metrics.requestCount++;
    this.metrics.totalDuration += duration;
    this.metrics.lastRequest = new Date();
    
    if (!success) {
      this.metrics.errorCount++;
    }
  }

  getAverageResponseTime() {
    return this.metrics.requestCount > 0 
      ? this.metrics.totalDuration / this.metrics.requestCount 
      : 0;
  }

  getErrorRate() {
    return this.metrics.requestCount > 0 
      ? this.metrics.errorCount / this.metrics.requestCount 
      : 0;
  }
}

Documentation Best Practices#

Code Documentation#

/**
 * LISA API Client for livestock monitoring and management
 * 
 * @class LISAPIClient
 * @param {string} integratorUID - Your integrator UID from BioCV
 * @param {string} baseURL - API base URL (default: production)
 * 
 * @example
 * const client = new LISAPIClient('your-integrator-uid');
 * const animals = await client.getAnimals('user123');
 */
class LISAPIClient {
  /**
   * Get all animals for a specific user
   * 
   * @param {string} userId - The user ID
   * @param {number} page - Page number (default: 1)
   * @param {number} limit - Items per page (default: 10, max: 100)
   * @returns {Promise<Object>} API response with animals data
   * 
   * @example
   * const animals = await client.getAnimals('user123', 1, 20);
   * console.log(`Found ${animals.data.length} animals`);
   */
  async getAnimals(userId, page = 1, limit = 10) {
    // Implementation...
  }
}

Next: Troubleshooting - Complete troubleshooting guide

Diese Seite wird aus dem offiziellen LiSA-API-Wiki erzeugt. Sollte etwas falsch sein oder fehlen, kontaktieren Sie uns — wir korrigieren es an der Quelle.

Wiki-Stand e01e79e · 19.8.2026