Skip to main content

Overview

Follow these best practices to ensure your Taxo API integration is secure, reliable, and performant in production environments.

Authentication & Security

  • Store API keys in secure environment variables, not in code
  • Rotate API keys regularly (quarterly recommended)
  • Use different API keys for different environments (dev, staging, prod)
  • Never log or expose API keys in error messages or logs
# Good: Using environment variables
export TAXO_API_KEY="your-secure-api-key"

# Bad: Hardcoding in source code
const API_KEY = "sk-1234567890abcdef"; // Never do this!
  • Store CIEC/FIEL passwords in encrypted vaults (AWS Secrets Manager, Azure Key Vault, etc.)
  • Never store credentials in plain text
  • Implement credential rotation procedures
  • Use least-privilege access principles
// Good: Using secure credential storage
const credentials = await secretsManager.getSecret('sat-credentials');

// Bad: Hardcoded credentials
const password = "plaintext-password"; // Never do this!
  • Always use HTTPS for API communications
  • Implement IP whitelisting when possible
  • Use VPN or private networks for sensitive integrations
  • Monitor for unusual API access patterns

Error Handling & Resilience

Retry Strategy

Implement exponential backoff for transient errors:
class TaxoClient {
  async withRetry(operation, maxRetries = 3) {
    let lastError;
    
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error;
        
        // Don't retry on client errors (4xx)
        if (error.response?.status >= 400 && error.response?.status < 500) {
          throw error;
        }
        
        if (attempt < maxRetries) {
          const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
          await this.sleep(delay);
          console.log(`Retry attempt ${attempt} after ${delay}ms`);
        }
      }
    }
    
    throw lastError;
  }

  async createExtraction(data) {
    return this.withRetry(() => 
      axios.post('/v1/extractions', data, {
        headers: { 'Authorization': `Bearer ${this.apiKey}` }
      })
    );
  }

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

Circuit Breaker Pattern

Implement circuit breaker to prevent cascading failures:
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureThreshold = threshold;
    this.timeout = timeout;
    this.failureCount = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }

  async call(operation) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.timeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }
}

Performance Optimization

Rate Limiting

Respect API rate limits to avoid throttling:
class RateLimiter {
  constructor(requestsPerMinute = 100) {
    this.limit = requestsPerMinute;
    this.requests = [];
  }

  async waitForAvailableSlot() {
    const now = Date.now();
    
    // Remove requests older than 1 minute
    this.requests = this.requests.filter(time => now - time < 60000);
    
    if (this.requests.length >= this.limit) {
      const oldestRequest = Math.min(...this.requests);
      const waitTime = 60000 - (now - oldestRequest);
      
      if (waitTime > 0) {
        await new Promise(resolve => setTimeout(resolve, waitTime));
        return this.waitForAvailableSlot(); // Recursive check
      }
    }
    
    this.requests.push(now);
  }
}

// Usage
const rateLimiter = new RateLimiter(100); // 100 requests per minute

async function makeAPICall() {
  await rateLimiter.waitForAvailableSlot();
  // Make your API call here
}

Batch Processing

Process documents in batches for better performance:
class BatchProcessor {
  constructor(batchSize = 10, concurrency = 3) {
    this.batchSize = batchSize;
    this.concurrency = concurrency;
  }

  async processBatch(items, processor) {
    const batches = this.createBatches(items);
    const results = [];
    
    for (let i = 0; i < batches.length; i += this.concurrency) {
      const batchGroup = batches.slice(i, i + this.concurrency);
      const batchPromises = batchGroup.map(batch => 
        this.processBatchItems(batch, processor)
      );
      
      const batchResults = await Promise.allSettled(batchPromises);
      results.push(...batchResults);
    }
    
    return results;
  }

  createBatches(items) {
    const batches = [];
    for (let i = 0; i < items.length; i += this.batchSize) {
      batches.push(items.slice(i, i + this.batchSize));
    }
    return batches;
  }

  async processBatchItems(batch, processor) {
    const results = [];
    for (const item of batch) {
      try {
        const result = await processor(item);
        results.push({ success: true, data: result });
      } catch (error) {
        results.push({ success: false, error: error.message });
      }
    }
    return results;
  }
}

Monitoring & Observability

Logging

Implement structured logging for better debugging:
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'taxo-integration' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Usage
logger.info('Creating extraction', {
  rfc: 'ABC123456789',
  period: '2024-01',
  informationType: 'INVOICE'
});

logger.error('Extraction failed', {
  rfc: 'ABC123456789',
  jobId: 'JOB123',
  error: error.message,
  stack: error.stack
});

Health Checks

Implement health check endpoints:
class HealthChecker {
  async checkHealth() {
    const checks = await Promise.allSettled([
      this.checkTaxoAPI(),
      this.checkDatabase(),
      this.checkExternalServices()
    ]);

    const results = {
      status: 'healthy',
      timestamp: new Date().toISOString(),
      checks: {}
    };

    checks.forEach((check, index) => {
      const checkName = ['TaxoAPI', 'Database', 'ExternalServices'][index];
      
      if (check.status === 'fulfilled') {
        results.checks[checkName] = { status: 'healthy', ...check.value };
      } else {
        results.checks[checkName] = { 
          status: 'unhealthy', 
          error: check.reason.message 
        };
        results.status = 'unhealthy';
      }
    });

    return results;
  }

  async checkTaxoAPI() {
    const response = await fetch('https://api.taxo.co/v1/health', {
      headers: { 'Authorization': `Bearer ${this.apiKey}` }
    });
    
    if (!response.ok) {
      throw new Error(`Taxo API unhealthy: ${response.status}`);
    }
    
    return { responseTime: Date.now() - startTime };
  }
}

Document Storage

Simple Storage Tips

  • Organize downloaded documents by date and RFC
  • Use meaningful file names (e.g., INVOICE_RFC123_2024-01-15.xml)
  • Keep XML and PDF versions together in the same folder
  • Create separate folders for different document types
  • Keep backups of all downloaded documents
  • Follow legal requirements for document retention (typically 5-10 years)
  • Store documents in a secure location
  • Regularly verify backup integrity

Testing Strategy

Integration Testing

describe('Taxo Integration', () => {
  let taxoClient;
  
  beforeEach(() => {
    taxoClient = new TaxoClient(process.env.TEST_API_KEY);
  });

  test('should create and complete extraction', async () => {
    // Create extraction
    const extraction = await taxoClient.createExtraction({
      subject: { identifier: 'TEST123456789' },
      credentials: { /* test credentials */ },
      options: {
        informationType: 'INVOICE',
        period: { from: '2024-01-01', to: '2024-01-31' }
      }
    });

    expect(extraction.publicId).toBeDefined();
    expect(extraction.status).toBe('PENDING');

    // Wait for completion (with timeout)
    const completed = await taxoClient.waitForCompletion(
      extraction.publicId, 
      { timeout: 300000 }
    );

    expect(completed.status).toBe('COMPLETED');
    expect(completed.completedCount).toBeGreaterThan(0);
  }, 10 * 60 * 1000); // 10 minute timeout

  test('should handle invalid credentials gracefully', async () => {
    await expect(
      taxoClient.createExtraction({
        subject: { identifier: 'INVALID123' },
        credentials: { SAT: { type: 'USERNAME_PASSWORD', username: 'invalid', password: 'invalid' } },
        options: { informationType: 'INVOICE', period: { from: '2024-01-01', to: '2024-01-31' } }
      })
    ).rejects.toThrow(/credentials/i);
  });
});

Environment Setup

Basic Configuration

  • Use different API keys for testing and production
  • Store sensitive information in environment variables
  • Never commit credentials to version control
  • Test your configuration before going live
  • Log all API requests and responses
  • Monitor extraction success/failure rates
  • Set up alerts for repeated failures
  • Keep track of API usage limits

Security Checklist

Next Steps