# Unified Messaging API - Client Development Guide

```
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│  🤖 Creating API clients with AI engines?                          │
│                                                                     │
│  Download this guide:                                              │
│  https://apiaccess.digify.no/client-guide                          │
│                                                                     │
│  This document is optimized for Claude Code and other AI tools     │
│  to help generate production-ready API client implementations.     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

## Quick Start

**Base URL:** `https://apiaccess.digify.no/api/v1`

**Authentication:** All endpoints require an `AccessToken` provided via:
- Query parameter: `?AccessToken=YOUR_TOKEN`
- Header: `X-Access-Token: YOUR_TOKEN`
- Header: `Authorization: Bearer YOUR_TOKEN`

**Response Format:** All responses are JSON with consistent structure:
- Success: `{"status": "success", ...}`
- Error: `{"status": "error", "error": {...}}`

---

## Table of Contents

1. [Authentication](#authentication)
2. [Endpoints](#endpoints)
   - [Send Messages](#1-send-messages)
   - [Get SMS Logs](#2-get-sms-logs)
   - [Get Email Logs](#3-get-email-logs)
   - [Search Companies](#4-search-companies)
   - [Get Company by OrgNr](#5-get-company-by-organization-number)
   - [Health Check](#6-health-check)
3. [Error Handling](#error-handling)
4. [Rate Limiting](#rate-limiting)
5. [TypeScript Types](#typescript-types)
6. [Example Implementations](#example-implementations)

---

## Authentication

The API uses token-based authentication. Obtain your `AccessToken` from the administrator.

**Token Requirements:**
- Length: 22 characters
- Case-sensitive
- Must be included in every request (except health checks)

**Example:**
```http
GET /api/v1/companies/search?search=DNB&AccessToken=yYuX5ZtV4kCpbXcZgBA
```

---

## Endpoints

### 1. Send Messages

Send SMS and/or Email messages through a unified endpoint.

**Method:** `POST`
**Path:** `/api/v1/messages/send`
**Auth:** Required

#### Request Body Schema

```json
{
  "messages": [
    {
      "type": "sms" | "email",
      // SMS-specific fields or Email-specific fields
    }
  ]
}
```

#### SMS Message Object

```json
{
  "type": "sms",
  "id": "optional-client-reference",
  "destAddr": "4712345678",
  "msg": "Your message text here",
  "sourceAddr": "1234",
  "sourceAddrTon": 1,
  "sourceAddrNpi": 1,
  "esmClass": 0,
  "protocolId": 0,
  "priority": 0,
  "dataCoding": 1,
  "sendWhen": "2025-01-01T12:00:00Z",
  "scheduleDeliveryTime": "2025-01-01T12:00:00Z",
  "validityPeriod": "2025-01-02T12:00:00Z"
}
```

**Required Fields:** `type`, `destAddr`, `msg`

**Field Descriptions:**

| Field | Type | Required | Max Length | Description |
|-------|------|----------|------------|-------------|
| type | string | ✓ | - | Must be "sms" |
| id | string | - | - | Client-provided reference ID |
| destAddr | string | ✓ | 15 | Recipient phone number |
| msg | string | ✓ | 1600 | Message content |
| sourceAddr | string | - | 15 | Sender address (default: "1234567890") |
| sourceAddrTon | number | - | - | Type of Number (0-255, default: 1) |
| sourceAddrNpi | number | - | - | Numbering Plan Indicator (0-255, default: 1) |
| esmClass | number | - | - | ESM class (0-255, default: 0) |
| protocolId | number | - | - | Protocol ID (0-255, default: 0) |
| priority | number | - | - | Priority level (0-255, default: 0) |
| dataCoding | number | - | - | Data coding scheme (0-255, default: 1) |
| sendWhen | string/date | - | - | When to send (ISO 8601 format) |
| scheduleDeliveryTime | string/date | - | - | Scheduled delivery time |
| validityPeriod | string/date | - | - | Message validity period |

#### Email Message Object

```json
{
  "type": "email",
  "sourceAddr": "noreply@example.com",
  "sourceAddrName": "Company Name",
  "destAddr": "user@example.com",
  "destAddrName": "User Name",
  "subject": "Email Subject",
  "body": "<html><body><h1>Email Content</h1></body></html>",
  "bodyFormat": "html",
  "emailProviderId": 2
}
```

**Required Fields:** `type`, `sourceAddr`, `destAddr`, `subject`, `body`

**Field Descriptions:**

| Field | Type | Required | Max Length | Description |
|-------|------|----------|------------|-------------|
| type | string | ✓ | - | Must be "email" |
| sourceAddr | string | ✓ | 100 | Sender email address (must be valid email) |
| sourceAddrName | string | - | 200 | Sender display name |
| destAddr | string | ✓ | 500 | Recipient email address (must be valid email) |
| destAddrName | string | - | 200 | Recipient display name |
| subject | string | ✓ | 500 | Email subject line |
| body | string | ✓ | 1048576 | Email body (1MB max) |
| bodyFormat | string | - | - | "html" or "text" (default: "html") |
| emailProviderId | number | - | - | Email provider ID (default: 2) |

#### Success Response (200 OK)

```json
{
  "status": "success",
  "simulationMode": false,
  "results": [
    {
      "id": "optional-client-reference",
      "type": "sms",
      "status": "queued",
      "messageId": 123456,
      "msgCount": 1
    },
    {
      "type": "email",
      "status": "queued",
      "emailId": 789
    }
  ],
  "summary": {
    "total": 2,
    "sms": 1,
    "email": 1,
    "creditsUsed": 1
  }
}
```

#### Example Request

```bash
curl -X POST "https://apiaccess.digify.no/api/v1/messages/send?AccessToken=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "type": "sms",
        "destAddr": "4712345678",
        "msg": "Hello from API!"
      },
      {
        "type": "email",
        "sourceAddr": "noreply@example.com",
        "destAddr": "user@example.com",
        "subject": "Test Email",
        "body": "<h1>Hello!</h1>"
      }
    ]
  }'
```

---

### 2. Get SMS Logs

Retrieve SMS message logs for your system.

**Method:** `GET`
**Path:** `/api/v1/logs/sms`
**Auth:** Required

#### Query Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| limit | number | - | 50 | Number of records (1-100) |
| offset | number | - | 0 | Records to skip |
| status | string | - | - | Filter by status |
| startDate | string | - | - | Filter start date (ISO 8601) |
| endDate | string | - | - | Filter end date (ISO 8601) |

#### Success Response (200 OK)

```json
{
  "status": "success",
  "data": [
    {
      "messageId": 10798634,
      "destAddr": "4791748750",
      "msg": "Test SMS",
      "msgCount": 1,
      "status": "queued",
      "createdAt": "2025-10-18T11:34:15.000Z",
      "sendWhen": "2025-10-18T11:34:15.000Z",
      "sourceAddr": "1234567890"
    }
  ],
  "pagination": {
    "total": 150,
    "limit": 50,
    "offset": 0,
    "hasMore": true
  }
}
```

#### Example Request

```bash
curl "https://apiaccess.digify.no/api/v1/logs/sms?AccessToken=YOUR_TOKEN&limit=10&offset=0"
```

---

### 3. Get Email Logs

Retrieve email message logs for your system.

**Method:** `GET`
**Path:** `/api/v1/logs/email`
**Auth:** Required

#### Query Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| limit | number | - | 50 | Number of records (1-100) |
| offset | number | - | 0 | Records to skip |
| status | string | - | - | Filter by status |
| startDate | string | - | - | Filter start date (ISO 8601) |
| endDate | string | - | - | Filter end date (ISO 8601) |

#### Success Response (200 OK)

```json
{
  "status": "success",
  "data": [
    {
      "emailId": 549870,
      "sourceAddr": "noreply@example.com",
      "destAddr": "user@example.com",
      "subject": "Test Email",
      "status": "queued",
      "createdAt": "2025-10-18T11:34:18.000Z",
      "bodyFormat": "html"
    }
  ],
  "pagination": {
    "total": 75,
    "limit": 50,
    "offset": 0,
    "hasMore": true
  }
}
```

#### Example Request

```bash
curl "https://apiaccess.digify.no/api/v1/logs/email?AccessToken=YOUR_TOKEN&limit=10&offset=0"
```

---

### 4. Search Companies

Search for Norwegian companies by name or organization number.

**Method:** `GET`
**Path:** `/api/v1/companies/search`
**Auth:** Required

#### Query Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| search | string | ✓ | Company name or 9-digit organization number |

**Search Behavior:**
- If `search` is exactly 9 numeric digits → searches by organization number
- Otherwise → searches by company name (prefix match)
- Returns maximum 5 results
- Name searches exclude hidden and deleted companies

#### Success Response (200 OK)

```json
{
  "status": "success",
  "companies": [
    {
      "orgNr": "987654321",
      "navn": "Example Company AS",
      "forrAdrPostSted": "Oslo"
    },
    {
      "orgNr": "123456789",
      "navn": "Another Example AS",
      "forrAdrPostSted": "Bergen"
    }
  ]
}
```

**Response Fields:**

| Field | Type | Description |
|-------|------|-------------|
| orgNr | string | 9-digit organization number |
| navn | string | Company name |
| forrAdrPostSted | string | City/postal location |

#### Example Requests

```bash
# Search by company name
curl "https://apiaccess.digify.no/api/v1/companies/search?search=DNB&AccessToken=YOUR_TOKEN"

# Search by organization number
curl "https://apiaccess.digify.no/api/v1/companies/search?search=984851006&AccessToken=YOUR_TOKEN"
```

---

### 5. Get Company by Organization Number

Get a specific company by its 9-digit organization number.

**Method:** `GET`
**Path:** `/api/v1/companies/:orgNr`
**Auth:** Required

#### URL Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| orgNr | string | ✓ | Exactly 9 numeric digits |

#### Success Response (200 OK)

Returns comprehensive company information with all available fields:

```json
{
  "status": "success",
  "companies": [
    {
      "orgnr": 984851006,
      "navn": "DNB BANK ASA",
      "organisasjonsform": "ASA",
      "forretningsadr": "Dronning Eufemias gate 30",
      "forradrpostnr": "0191",
      "forradrpoststed": "OSLO",
      "forradrkommnr": 301,
      "forradrkommnavn": "OSLO",
      "forradrland": "Norge",
      "postadresse": "Postboks 1600 Sentrum",
      "ppostnr": "0021",
      "ppoststed": "OSLO",
      "ppostland": "Norge",
      "regifr": true,
      "regimva": true,
      "nkode1": "64.190",
      "nkode2": null,
      "nkode3": null,
      "sektorkode": 3200,
      "konkurs": false,
      "avvikling": false,
      "tvangsavvikling": false,
      "regiaa": false,
      "regifriv": false,
      "regdato": "2002-09-12T00:00:00.000Z",
      "stiftelsesdato": "2002-09-10T00:00:00.000Z",
      "tlf": null,
      "tlf_mobil": "4791504800",
      "url": "www.dnb.no",
      "regnskap": 2023,
      "hovedenhet": null,
      "ansatte_antall": 8343,
      "ansatte_dato": "2025-02-12T00:00:00.000Z",
      "Slettet": false,
      "Hidden": false
    }
  ]
}
```

**Field Descriptions:**

| Field | Type | Description |
|-------|------|-------------|
| orgnr | number | Organization number |
| navn | string | Company name |
| organisasjonsform | string | Organization form (AS, ASA, etc.) |
| forretningsadr | string | Business address street |
| forradrpostnr | string | Business address postal code |
| forradrpoststed | string | Business address city |
| forradrkommnr | number | Business address municipality number |
| forradrkommnavn | string | Business address municipality name |
| forradrland | string | Business address country |
| postadresse | string | Postal address |
| ppostnr | string | Postal code |
| ppoststed | string | Postal city |
| ppostland | string | Postal country |
| regifr | boolean | Registered in Foretaksregisteret |
| regimva | boolean | Registered for VAT |
| nkode1 | string | Primary industry code (NACE) |
| nkode2 | string | Secondary industry code |
| nkode3 | string | Tertiary industry code |
| sektorkode | number | Sector code |
| konkurs | boolean | In bankruptcy |
| avvikling | boolean | In liquidation |
| tvangsavvikling | boolean | In forced liquidation |
| regiaa | boolean | Registered in Employer Register |
| regifriv | boolean | Registered for voluntary organizations |
| regdato | string | Registration date (ISO 8601) |
| stiftelsesdato | string | Foundation date (ISO 8601) |
| tlf | string | Phone number |
| tlf_mobil | string | Mobile phone |
| url | string | Website URL |
| regnskap | number | Latest accounting year |
| hovedenhet | number | Parent organization number |
| ansatte_antall | number | Number of employees |
| ansatte_dato | string | Employee count date (ISO 8601) |
| Slettet | boolean | Deleted status |
| Hidden | boolean | Hidden status |

#### Example Request

```bash
curl "https://apiaccess.digify.no/api/v1/companies/984851006?AccessToken=YOUR_TOKEN"
```

---

### 6. Health Check

Check if the API service is healthy and operational.

**Method:** `GET`
**Path:** `/health`
**Auth:** Not required

#### Success Response (200 OK)

```json
{
  "status": "healthy",
  "timestamp": "2025-10-18T12:00:00.000Z",
  "environment": "production",
  "database": "connected"
}
```

#### Unhealthy Response (503 Service Unavailable)

```json
{
  "status": "unhealthy",
  "timestamp": "2025-10-18T12:00:00.000Z",
  "environment": "production",
  "database": "disconnected",
  "error": "Connection timeout"
}
```

#### Example Request

```bash
curl "https://apiaccess.digify.no/health"
```

---

## Error Handling

All errors follow a consistent JSON structure:

```json
{
  "status": "error",
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "retryAfter": 60,
    "details": {}
  }
}
```

### Error Codes

| HTTP Status | Error Code | Description | Retry Strategy |
|-------------|------------|-------------|----------------|
| 400 | VALIDATION_ERROR | Invalid request data | Fix request and retry |
| 401 | AUTHENTICATION_FAILED | Invalid/missing AccessToken | Check token and retry |
| 402 | INSUFFICIENT_CREDIT | Not enough SMS credits | Add credits or wait |
| 404 | NOT_FOUND | Endpoint not found | Check URL |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests | Wait `retryAfter` seconds |
| 500 | DATABASE_ERROR | Database operation failed | Retry with backoff |
| 500 | INTERNAL_SERVER_ERROR | Unexpected error | Retry with backoff |

### Example Error Responses

**Validation Error (400)**
```json
{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request data",
    "details": [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "number",
        "path": ["messages", 0, "destAddr"],
        "message": "Expected string, received number"
      }
    ]
  }
}
```

**Authentication Error (401)**
```json
{
  "status": "error",
  "error": {
    "code": "AUTHENTICATION_FAILED",
    "message": "Invalid AccessToken"
  }
}
```

**Rate Limit Error (429)**
```json
{
  "status": "error",
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded for SMS. Limit: 60. Current: 60. Try again in 15 seconds.",
    "retryAfter": 15,
    "details": {
      "messageType": "SMS",
      "limit": 60,
      "currentCount": 60
    }
  }
}
```

**Note:** Rate limit responses include a `Retry-After` header (in seconds).

**Insufficient Credit Error (402)**
```json
{
  "status": "error",
  "error": {
    "code": "INSUFFICIENT_CREDIT",
    "message": "Insufficient SMS credits. Required: 5, Available: 2",
    "details": {
      "creditsNeeded": 5,
      "creditsAvailable": 2
    }
  }
}
```

---

## Rate Limiting

The API enforces rate limits based on your system's environment configuration.

### Default Limits

| Environment | Type | Per Minute | Per Hour | Per Day |
|-------------|------|------------|----------|---------|
| Development | SMS | 6 | 60 | 500 |
| Development | Email | 60 | 500 | 5,000 |
| Production | SMS | 60 | 3,000 | 50,000 |
| Production | Email | 300 | 10,000 | 100,000 |

### Handling Rate Limits

When you receive a `429` error:

1. Extract `retryAfter` from the error response
2. Wait the specified number of seconds
3. Retry the request
4. Implement exponential backoff for repeated 429 responses

**Example retry logic:**
```javascript
async function sendWithRetry(request, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await sendRequest(request);
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries - 1) {
        const retryAfter = error.error.retryAfter || 60;
        await sleep(retryAfter * 1000);
        continue;
      }
      throw error;
    }
  }
}
```

---

## TypeScript Types

Use these TypeScript interfaces when building clients:

```typescript
// ============================================
// Request Types
// ============================================

interface SMSMessage {
  type: 'sms';
  id?: string;
  destAddr: string;
  msg: string;
  sourceAddr?: string;
  sourceAddrTon?: number;
  sourceAddrNpi?: number;
  esmClass?: number;
  protocolId?: number;
  priority?: number;
  dataCoding?: number;
  sendWhen?: string | Date;
  scheduleDeliveryTime?: string | Date;
  validityPeriod?: string | Date;
}

interface EmailMessage {
  type: 'email';
  sourceAddr: string;
  sourceAddrName?: string;
  destAddr: string;
  destAddrName?: string;
  subject: string;
  body: string;
  bodyFormat?: 'html' | 'text';
  emailProviderId?: number;
}

type Message = SMSMessage | EmailMessage;

interface SendMessagesRequest {
  messages: Message[];
}

interface SMSLogsQuery {
  limit?: number;
  offset?: number;
  status?: string;
  startDate?: string;
  endDate?: string;
}

interface EmailLogsQuery {
  limit?: number;
  offset?: number;
  status?: string;
  startDate?: string;
  endDate?: string;
}

interface CompanySearchQuery {
  search: string;
}

// ============================================
// Response Types
// ============================================

interface SMSResult {
  id?: string;
  type: 'sms';
  status: 'queued' | 'simulated';
  messageId: number;
  msgCount: number;
}

interface EmailResult {
  type: 'email';
  status: 'queued' | 'simulated';
  emailId: number;
}

type MessageResult = SMSResult | EmailResult;

interface SendMessagesResponse {
  status: 'success';
  simulationMode: boolean;
  results: MessageResult[];
  summary: {
    total: number;
    sms: number;
    email: number;
    creditsUsed: number;
  };
}

interface SMSLog {
  messageId: number;
  destAddr: string;
  msg: string;
  msgCount: number;
  status: string;
  createdAt: string;
  sendWhen: string;
  sourceAddr: string;
}

interface EmailLog {
  emailId: number;
  sourceAddr: string;
  destAddr: string;
  subject: string;
  status: string;
  createdAt: string;
  bodyFormat: string;
}

interface Pagination {
  total: number;
  limit: number;
  offset: number;
  hasMore: boolean;
}

interface SMSLogsResponse {
  status: 'success';
  data: SMSLog[];
  pagination: Pagination;
}

interface EmailLogsResponse {
  status: 'success';
  data: EmailLog[];
  pagination: Pagination;
}

interface Company {
  orgNr: string;
  navn: string;
  forrAdrPostSted: string;
}

interface CompanySearchResponse {
  status: 'success';
  companies: Company[];
}

interface HealthResponse {
  status: 'healthy' | 'unhealthy';
  timestamp: string;
  environment: string;
  database: 'connected' | 'disconnected';
  error?: string;
}

interface ErrorResponse {
  status: 'error';
  error: {
    code: string;
    message: string;
    retryAfter?: number;
    details?: any;
  };
}
```

---

## Example Implementations

### JavaScript/TypeScript Client

```typescript
class MessagingAPIClient {
  private baseUrl: string;
  private accessToken: string;

  constructor(accessToken: string, baseUrl = 'https://apiaccess.digify.no/api/v1') {
    this.baseUrl = baseUrl;
    this.accessToken = accessToken;
  }

  private async request<T>(
    method: string,
    path: string,
    body?: any,
    queryParams?: Record<string, string>
  ): Promise<T> {
    const url = new URL(`${this.baseUrl}${path}`);
    url.searchParams.set('AccessToken', this.accessToken);

    if (queryParams) {
      Object.entries(queryParams).forEach(([key, value]) => {
        url.searchParams.set(key, value);
      });
    }

    const response = await fetch(url.toString(), {
      method,
      headers: {
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    const data = await response.json();

    if (data.status === 'error') {
      throw new APIError(data.error, response.status);
    }

    return data;
  }

  async sendMessages(messages: Message[]): Promise<SendMessagesResponse> {
    return this.request<SendMessagesResponse>('POST', '/messages/send', { messages });
  }

  async getSMSLogs(query?: SMSLogsQuery): Promise<SMSLogsResponse> {
    return this.request<SMSLogsResponse>('GET', '/logs/sms', undefined, query as any);
  }

  async getEmailLogs(query?: EmailLogsQuery): Promise<EmailLogsResponse> {
    return this.request<EmailLogsResponse>('GET', '/logs/email', undefined, query as any);
  }

  async searchCompanies(search: string): Promise<CompanySearchResponse> {
    return this.request<CompanySearchResponse>('GET', '/companies/search', undefined, { search });
  }

  async getCompanyByOrgNr(orgNr: string): Promise<CompanySearchResponse> {
    return this.request<CompanySearchResponse>('GET', `/companies/${orgNr}`);
  }

  async healthCheck(): Promise<HealthResponse> {
    const url = this.baseUrl.replace('/api/v1', '/health');
    const response = await fetch(url);
    return response.json();
  }
}

class APIError extends Error {
  constructor(public error: ErrorResponse['error'], public status: number) {
    super(error.message);
    this.name = 'APIError';
  }
}

// Usage example
const client = new MessagingAPIClient('YOUR_ACCESS_TOKEN');

// Send SMS
await client.sendMessages([
  {
    type: 'sms',
    destAddr: '4712345678',
    msg: 'Hello from TypeScript client!',
  },
]);

// Search companies
const companies = await client.searchCompanies('DNB');
console.log(companies.companies);

// Get SMS logs
const logs = await client.getSMSLogs({ limit: 10, offset: 0 });
console.log(logs.data);
```

### Python Client

```python
import requests
from typing import List, Dict, Optional, Union
from datetime import datetime

class MessagingAPIClient:
    def __init__(self, access_token: str, base_url: str = 'https://apiaccess.digify.no/api/v1'):
        self.base_url = base_url
        self.access_token = access_token

    def _request(self, method: str, path: str, json_data=None, params=None) -> Dict:
        url = f"{self.base_url}{path}"

        # Add AccessToken to params
        if params is None:
            params = {}
        params['AccessToken'] = self.access_token

        response = requests.request(
            method=method,
            url=url,
            json=json_data,
            params=params,
            headers={'Content-Type': 'application/json'}
        )

        data = response.json()

        if data.get('status') == 'error':
            raise APIError(data['error'], response.status_code)

        return data

    def send_messages(self, messages: List[Dict]) -> Dict:
        return self._request('POST', '/messages/send', json_data={'messages': messages})

    def get_sms_logs(self, limit: int = 50, offset: int = 0, **kwargs) -> Dict:
        params = {'limit': limit, 'offset': offset, **kwargs}
        return self._request('GET', '/logs/sms', params=params)

    def get_email_logs(self, limit: int = 50, offset: int = 0, **kwargs) -> Dict:
        params = {'limit': limit, 'offset': offset, **kwargs}
        return self._request('GET', '/logs/email', params=params)

    def search_companies(self, search: str) -> Dict:
        return self._request('GET', '/companies/search', params={'search': search})

    def get_company_by_orgnr(self, org_nr: str) -> Dict:
        return self._request('GET', f'/companies/{org_nr}')

    def health_check(self) -> Dict:
        url = self.base_url.replace('/api/v1', '/health')
        response = requests.get(url)
        return response.json()

class APIError(Exception):
    def __init__(self, error: Dict, status_code: int):
        self.error = error
        self.status_code = status_code
        super().__init__(error['message'])

# Usage example
client = MessagingAPIClient('YOUR_ACCESS_TOKEN')

# Send SMS
response = client.send_messages([
    {
        'type': 'sms',
        'destAddr': '4712345678',
        'msg': 'Hello from Python client!'
    }
])

# Search companies
companies = client.search_companies('DNB')
print(companies['companies'])

# Get SMS logs
logs = client.get_sms_logs(limit=10)
print(logs['data'])
```

### C# Client

```csharp
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;

public class MessagingAPIClient
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;
    private readonly string _accessToken;

    public MessagingAPIClient(string accessToken, string baseUrl = "https://apiaccess.digify.no/api/v1")
    {
        _baseUrl = baseUrl;
        _accessToken = accessToken;
        _httpClient = new HttpClient();
    }

    private async Task<T> RequestAsync<T>(HttpMethod method, string path, object body = null, Dictionary<string, string> queryParams = null)
    {
        var uriBuilder = new UriBuilder($"{_baseUrl}{path}");
        var query = System.Web.HttpUtility.ParseQueryString(uriBuilder.Query);
        query["AccessToken"] = _accessToken;

        if (queryParams != null)
        {
            foreach (var param in queryParams)
            {
                query[param.Key] = param.Value;
            }
        }

        uriBuilder.Query = query.ToString();

        var request = new HttpRequestMessage(method, uriBuilder.Uri);

        if (body != null)
        {
            var json = JsonSerializer.Serialize(body);
            request.Content = new StringContent(json, Encoding.UTF8, "application/json");
        }

        var response = await _httpClient.SendAsync(request);
        var content = await response.Content.ReadAsStringAsync();
        var data = JsonSerializer.Deserialize<T>(content);

        return data;
    }

    public Task<SendMessagesResponse> SendMessagesAsync(List<object> messages)
    {
        return RequestAsync<SendMessagesResponse>(HttpMethod.Post, "/messages/send", new { messages });
    }

    public Task<SMSLogsResponse> GetSMSLogsAsync(int limit = 50, int offset = 0)
    {
        var query = new Dictionary<string, string>
        {
            ["limit"] = limit.ToString(),
            ["offset"] = offset.ToString()
        };
        return RequestAsync<SMSLogsResponse>(HttpMethod.Get, "/logs/sms", null, query);
    }

    public Task<CompanySearchResponse> SearchCompaniesAsync(string search)
    {
        var query = new Dictionary<string, string> { ["search"] = search };
        return RequestAsync<CompanySearchResponse>(HttpMethod.Get, "/companies/search", null, query);
    }

    public Task<CompanySearchResponse> GetCompanyByOrgNrAsync(string orgNr)
    {
        return RequestAsync<CompanySearchResponse>(HttpMethod.Get, $"/companies/{orgNr}");
    }
}

// Usage example
var client = new MessagingAPIClient("YOUR_ACCESS_TOKEN");

var response = await client.SendMessagesAsync(new List<object>
{
    new { type = "sms", destAddr = "4712345678", msg = "Hello from C# client!" }
});

var companies = await client.SearchCompaniesAsync("DNB");
```

---

## Best Practices

### 1. Error Handling
- Always check the `status` field in responses
- Implement retry logic for 429 (rate limit) and 500 (server error) responses
- Use exponential backoff for retries
- Log errors with full context for debugging

### 2. Rate Limiting
- Respect the `Retry-After` header/field in 429 responses
- Implement client-side rate limiting to stay under limits
- Batch messages when possible to reduce API calls

### 3. Message Batching
- Send multiple messages in a single request when possible
- Maximum 100 messages per request
- Batching reduces overhead and improves throughput

### 4. Authentication
- Store AccessToken securely (environment variables, secrets manager)
- Never hard-code tokens in source code
- Rotate tokens periodically

### 5. Logging
- Log all API requests and responses (excluding sensitive data)
- Include request IDs for tracing
- Monitor error rates and response times

### 6. Testing
- Use simulation mode for development/testing
- Test error handling with invalid data
- Verify retry logic works correctly

### 7. Monitoring
- Track API usage and credit consumption
- Set up alerts for rate limit approaching
- Monitor error rates and success rates

---

## Support

For issues, questions, or feature requests:

- **Documentation:** https://apiaccess.digify.no/
- **Health Status:** https://apiaccess.digify.no/health
- **Technical Support:** Contact your system administrator

---

**Document Version:** 1.0
**Last Updated:** 2025-11-01
**API Version:** v1
