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

Webhook Setup

Configure real-time notifications for your integration.

Error Handling

Learn how to handle and troubleshoot common issues.

Use Cases

Explore specific implementation patterns for common use cases.

API Reference

Dive deep into the complete API documentation.