> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kairosafrika.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Development Guide

> Best practices for developing with Kairos Afrika APIs

## Development Environment Setup

<Info>
  **Recommended**: Always start development using our staging environment to avoid affecting live data.
</Info>

### Environment Configuration

Configure your application to use the appropriate environment:

<CodeGroup>
  ```javascript JavaScript theme={null}
  // Environment configuration
  const config = {
    staging: {
      // SMS, USSD, Checkout APIs
      baseUrl: 'https://api.staging.kairosafrika.com/v1',
      // Payment API
      paymentBaseUrl: 'https://api.staging.kairosafrika.cloud/paygw',
      apiKey: process.env.KAIROS_STAGING_API_KEY
    },
    production: {
      // SMS, USSD, Checkout APIs
      baseUrl: 'https://api.kairosafrika.com/v1',
      // Payment API
      paymentBaseUrl: 'https://api.kairosafrika.cloud/paygw',
      apiKey: process.env.KAIROS_PRODUCTION_API_KEY
    }
  };

  const environment = process.env.NODE_ENV === 'production' ? 'production' : 'staging';
  const apiConfig = config[environment];
  ```

  ```python Python theme={null}
  import os

  # Environment configuration
  CONFIG = {
      'staging': {
          # SMS, USSD, Checkout APIs
          'base_url': 'https://api.staging.kairosafrika.com/v1',
          # Payment API
          'payment_base_url': 'https://api.staging.kairosafrika.cloud/paygw',
          'api_key': os.getenv('KAIROS_STAGING_API_KEY')
      },
      'production': {
          # SMS, USSD, Checkout APIs
          'base_url': 'https://api.kairosafrika.com/v1',
          # Payment API
          'payment_base_url': 'https://api.kairosafrika.cloud/paygw',
          'api_key': os.getenv('KAIROS_PRODUCTION_API_KEY')
      }
  }

  environment = 'production' if os.getenv('ENV') == 'production' else 'staging'
  api_config = CONFIG[environment]
  ```

  ```php PHP theme={null}
  <?php
  // Environment configuration
  $config = [
      'staging' => [
          // SMS, USSD, Checkout APIs
          'base_url' => 'https://api.staging.kairosafrika.com/v1',
          // Payment API
          'payment_base_url' => 'https://api.staging.kairosafrika.cloud/paygw',
          'api_key' => $_ENV['KAIROS_STAGING_API_KEY']
      ],
      'production' => [
          // SMS, USSD, Checkout APIs
          'base_url' => 'https://api.kairosafrika.com/v1',
          // Payment API
          'payment_base_url' => 'https://api.kairosafrika.cloud/paygw',
          'api_key' => $_ENV['KAIROS_PRODUCTION_API_KEY']
      ]
  ];

  $environment = $_ENV['ENV'] === 'production' ? 'production' : 'staging';
  $apiConfig = $config[$environment];
  ?>
  ```
</CodeGroup>

## Error Handling

Implement robust error handling for all API calls:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function sendSMS(data) {
    try {
      const response = await fetch(`${apiConfig.baseUrl}/sms`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiConfig.apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(`API Error: ${error.message}`);
      }

      return await response.json();
    } catch (error) {
      console.error('SMS sending failed:', error);
      throw error;
    }
  }
  ```

  ```python Python theme={null}
  import requests
  from requests.exceptions import RequestException

  def send_sms(data):
      try:
          response = requests.post(
              f"{api_config['base_url']}/sms",
              headers={
                  'Authorization': f"Bearer {api_config['api_key']}",
                  'Content-Type': 'application/json'
              },
              json=data,
              timeout=30
          )

          response.raise_for_status()
          return response.json()

      except RequestException as e:
          print(f"SMS sending failed: {e}")
          raise
  ```
</CodeGroup>

## Rate Limiting

Handle rate limits gracefully:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function apiCallWithRetry(url, options, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const response = await fetch(url, options);

        if (response.status === 429) {
          const retryAfter = response.headers.get('Retry-After') || 60;
          console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          continue;
        }

        return response;
      } catch (error) {
        if (attempt === maxRetries) throw error;
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      }
    }
  }
  ```

  ```python Python theme={null}
  import time
  import requests
  from requests.exceptions import RequestException

  def api_call_with_retry(url, **kwargs):
      max_retries = 3

      for attempt in range(1, max_retries + 1):
          try:
              response = requests.request(**kwargs, url=url)

              if response.status_code == 429:
                  retry_after = int(response.headers.get('Retry-After', 60))
                  print(f"Rate limited. Retrying after {retry_after} seconds...")
                  time.sleep(retry_after)
                  continue

              return response

          except RequestException as e:
              if attempt == max_retries:
                  raise
              time.sleep(attempt)
  ```
</CodeGroup>

## Testing

### Unit Testing

<CodeGroup>
  ```javascript Jest theme={null}
  // tests/sms.test.js
  const { sendSMS } = require('../src/sms');

  // Mock the API
  jest.mock('node-fetch');
  const fetch = require('node-fetch');

  describe('SMS API', () => {
    beforeEach(() => {
      fetch.mockClear();
    });

    test('should send SMS successfully', async () => {
      const mockResponse = {
        messageId: 'msg_123',
        status: 'sent'
      };

      fetch.mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockResponse)
      });

      const result = await sendSMS({
        to: ['+254700000000'],
        message: 'Test message'
      });

      expect(result.messageId).toBe('msg_123');
      expect(fetch).toHaveBeenCalledWith(
        expect.stringContaining('/sms'),
        expect.objectContaining({
          method: 'POST'
        })
      );
    });
  });
  ```

  ```python pytest theme={null}
  # tests/test_sms.py
  import pytest
  from unittest.mock import patch, Mock
  from src.sms import send_sms

  @patch('src.sms.requests.post')
  def test_send_sms_success(mock_post):
      # Mock successful response
      mock_response = Mock()
      mock_response.json.return_value = {
          'messageId': 'msg_123',
          'status': 'sent'
      }
      mock_response.raise_for_status.return_value = None
      mock_post.return_value = mock_response

      result = send_sms({
          'to': ['+254700000000'],
          'message': 'Test message'
      })

      assert result['messageId'] == 'msg_123'
      mock_post.assert_called_once()
  ```
</CodeGroup>

## Logging and Monitoring

Implement comprehensive logging:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const winston = require('winston');

  const logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
    transports: [
      new winston.transports.File({ filename: 'api-calls.log' }),
      new winston.transports.Console()
    ]
  });

  async function loggedApiCall(endpoint, data) {
    const startTime = Date.now();

    try {
      logger.info('API call started', { endpoint, data });
      const result = await makeApiCall(endpoint, data);

      logger.info('API call successful', {
        endpoint,
        duration: Date.now() - startTime,
        resultId: result.id
      });

      return result;
    } catch (error) {
      logger.error('API call failed', {
        endpoint,
        duration: Date.now() - startTime,
        error: error.message
      });
      throw error;
    }
  }
  ```
</CodeGroup>

## Security Best Practices

<Warning>
  Never commit API keys to version control. Always use environment variables.
</Warning>

### API Key Management

1. **Environment Variables**: Store API keys in environment variables
2. **Key Rotation**: Regularly rotate your API keys
3. **Least Privilege**: Use separate keys for different environments
4. **Monitoring**: Monitor API key usage for unusual activity

### Request Security

* Always use HTTPS
* Validate all input data
* Implement request signing for sensitive operations
* Use webhook signature verification

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 Unauthorized Error">
    **Cause**: Invalid or missing API key

    **Solution**:

    * Verify your API key is correct
    * Check that the Authorization header is properly formatted
    * Ensure you're using the right key for the environment
  </Accordion>

  <Accordion title="429 Rate Limit Exceeded">
    **Cause**: Too many requests in a short period

    **Solution**:

    * Implement exponential backoff
    * Check the `Retry-After` header
    * Consider upgrading your plan for higher limits
  </Accordion>

  <Accordion title="Webhook Not Receiving Events">
    **Cause**: Webhook endpoint issues

    **Solution**:

    * Verify your webhook URL is accessible
    * Check webhook signature verification
    * Ensure your endpoint returns 200 status
    * Check webhook logs in your dashboard
  </Accordion>
</AccordionGroup>
