API-Referenz

Endpunkte, Datenmodelle und Fehlercodes der LiSA REST API, erzeugt aus dem offiziellen API-Wiki.
Wiki-Stand 158c1a5 · 28.7.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 158c1a5 · 28.7.2026
Wir schätzen Ihre Privatsphäre

Wir verwenden Cookies und ähnliche Technologien, um diese Website zu betreiben und mit Ihrer Einwilligung für Analysen und (falls zutreffend) Werbung. Sie können alle akzeptieren, alle ablehnen oder Ihre Auswahl anpassen. Einzelheiten finden Sie in unserer Datenschutzerklärung und Cookie-Richtlinie.

Kategorien: Notwendig (Immer aktiv), Funktional, Analytik, Marketing

Hallo! Brauchen Sie Hilfe? Chatten Sie mit mir!