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

# API Integration

> Connect your application to AGO using the SDK API or Public API

AGO provides two APIs for different integration scenarios:

* **SDK API** (`/api/sdk/v1/`) — For frontend and mobile apps. Authenticates with a client identifier and validates the request origin. Supports SSE streaming for real-time responses.
* **Public API v1** (`/api/v1/`) — For server-to-server integrations. Authenticates with an API key. Use this when your backend needs to send messages or manage conversations programmatically.

***

## Authentication

### SDK API (Frontend / Mobile)

Every request to `/api/sdk/v1/` requires these headers:

| Header                        | Required | Description                                                   |
| ----------------------------- | -------- | ------------------------------------------------------------- |
| `X-User-Anon-Id`              | Yes      | A unique client identifier you generate per device or session |
| `Origin`                      | Yes      | Must match one of your allowed domains                        |
| `Authorization: Bearer {jwt}` | No       | Optional JWT for authenticated users                          |

```javascript theme={null}
const SDK_HEADERS = {
  'X-User-Anon-Id': 'unique-client-id',
};
```

### Public API v1 (Server-to-Server)

Every request to `/api/v1/` requires a single header:

| Header      | Required | Description                                                             |
| ----------- | -------- | ----------------------------------------------------------------------- |
| `X-API-Key` | Yes      | Your API key (found in admin interface under Administration > API Keys) |

```javascript theme={null}
const API_HEADERS = {
  'X-API-Key': 'your-api-key',
  'Content-Type': 'application/json',
};
```

***

## SDK API Endpoints

### Send Message

`POST /api/sdk/v1/messages`

Sends a message and returns an SSE streaming response.

**Request body:**

```json theme={null}
{
  "content": "How do I reset my password?",
  "conversation_id": "optional-conversation-uuid",
  "agent_id": "optional-agent-uuid",
  "client_functions": [],
  "metadata": {}
}
```

Only `content` is required. Omit `conversation_id` to start a new conversation.

**Response:** Server-Sent Events stream (see [SSE Streaming](#sse-streaming-recommended) below).

### Get Message Status

`GET /api/sdk/v1/messages/{message_id}/status`

Poll this endpoint when SSE streaming is not available. Returns the current processing status and any content generated so far.

### Submit Feedback

`POST /api/sdk/v1/messages/{message_id}/feedback`

```json theme={null}
{
  "rating": "positive"
}
```

Accepts `"positive"` or `"negative"`.

### List Conversations

`GET /api/sdk/v1/conversations`

Returns a paginated list of conversations for the current user.

**Response:**

```json theme={null}
{
  "items": [...],
  "total": 42,
  "page": 1,
  "page_size": 20
}
```

### Get Conversation

`GET /api/sdk/v1/conversations/{conversation_id}`

Returns a single conversation with its full message history.

### Unread Count

`GET /api/sdk/v1/conversations/unread`

Returns the number of unread conversations.

### Widget Config

`GET /api/sdk/v1/config`

Returns widget configuration and available forms.

***

## Integration Patterns

### SSE Streaming (Recommended)

The send-message endpoint returns a Server-Sent Events stream. Each event is a JSON object on a `data:` line.

```javascript theme={null}
async function sendMessage(baseUrl, content, conversationId) {
  const response = await fetch(`${baseUrl}/api/sdk/v1/messages`, {
    method: 'POST',
    headers: {
      ...SDK_HEADERS,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content,
      conversation_id: conversationId || undefined,
    }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  let fullContent = '';
  let conversationInfo = null;
  let sources = [];

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    const lines = chunk.split('\n');

    for (const line of lines) {
      if (!line.startsWith('data: ')) continue;

      const data = JSON.parse(line.slice(6));

      if (data.conversation_id && !conversationInfo) {
        conversationInfo = data.conversation_id;
      }

      if (data.knowledge_sources) {
        sources = data.knowledge_sources;
      }

      if (data.content) {
        fullContent += data.content;
        onContentUpdate(fullContent); // Update UI progressively
      }

      if (data.status === 'DONE') {
        onComplete(fullContent, sources, conversationInfo);
      }
    }
  }

  return { content: fullContent, sources, conversationId: conversationInfo };
}
```

### Polling Fallback

If SSE is blocked by firewalls or proxies, poll the message status endpoint instead:

```javascript theme={null}
async function sendMessageWithPolling(baseUrl, content, conversationId) {
  // 1. Send the message
  const sendResponse = await fetch(`${baseUrl}/api/sdk/v1/messages`, {
    method: 'POST',
    headers: {
      ...SDK_HEADERS,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content,
      conversation_id: conversationId || undefined,
    }),
  });

  // Extract message ID from the first SSE event
  const reader = sendResponse.body.getReader();
  const { value } = await reader.read();
  const firstEvent = JSON.parse(new TextDecoder().decode(value).split('data: ')[1]);
  const messageId = firstEvent.message_id;
  reader.cancel();

  // 2. Poll for completion
  let status = 'IN_PROGRESS';
  let result = null;

  while (status === 'IN_PROGRESS') {
    await new Promise((resolve) => setTimeout(resolve, 1000));

    const statusResponse = await fetch(
      `${baseUrl}/api/sdk/v1/messages/${messageId}/status`,
      { headers: SDK_HEADERS }
    );

    result = await statusResponse.json();
    status = result.status;

    if (result.content) {
      onContentUpdate(result.content);
    }
  }

  return result;
}
```

### Conversation Management

Use `conversation_id` to maintain multi-turn conversations:

```javascript theme={null}
class ConversationManager {
  constructor(baseUrl, headers) {
    this.baseUrl = baseUrl;
    this.headers = headers;
  }

  async startConversation(content, agentId) {
    const response = await this.sendMessage(content, null, agentId);
    return response.conversationId;
  }

  async continueConversation(conversationId, content) {
    return this.sendMessage(content, conversationId);
  }

  async listConversations(page = 1) {
    const response = await fetch(
      `${this.baseUrl}/api/sdk/v1/conversations?page=${page}`,
      { headers: this.headers }
    );
    return response.json();
  }

  async getConversation(conversationId) {
    const response = await fetch(
      `${this.baseUrl}/api/sdk/v1/conversations/${conversationId}`,
      { headers: this.headers }
    );
    return response.json();
  }

  async sendMessage(content, conversationId, agentId) {
    // Use the SSE streaming pattern from above
  }
}
```

***

## Public API v1 (Server-to-Server)

Use the Public API when your backend needs to send messages on behalf of users or automate workflows.

### Send Message

`POST /api/v1/send-message`

```javascript theme={null}
const fetch = require('node-fetch');

const BASE_URL = 'https://your-ago-instance.com';
const API_KEY = process.env.AGO_API_KEY;

async function sendMessage(content, agentId) {
  const response = await fetch(`${BASE_URL}/api/v1/send-message`, {
    method: 'POST',
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content,
      agent_id: agentId,
    }),
  });

  let fullContent = '';
  let sources = [];

  for await (const chunk of response.body) {
    const text = chunk.toString();
    const lines = text.split('\n').filter((l) => l.startsWith('data: '));

    for (const line of lines) {
      const data = JSON.parse(line.slice(6));
      if (data.knowledge_sources) sources = data.knowledge_sources;
      if (data.content) fullContent += data.content;
    }
  }

  return { answer: fullContent, sources };
}

// Example: answer a support ticket from your backend
async function handleIncomingTicket(ticketContent) {
  const result = await sendMessage(ticketContent, 'your-agent-uuid');
  console.log('Answer:', result.answer);
  return result;
}
```

***

## Best Practices

* **Stream responses to users as they arrive.** Do not wait for the full response before displaying content.
* **Set timeouts to 60+ seconds** for streaming connections. AI responses can take time depending on the query.
* **Store `conversation_id`** from the first response and pass it back on subsequent messages to maintain context.
* **Never expose `X-API-Key` in frontend code.** The Public API is for server-to-server use only. Use the SDK API (`X-User-Anon-Id`) for frontend apps.
* **Implement retry with backoff** for transient errors (5xx, network timeouts).

```javascript theme={null}
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      if (response.status === 429) {
        await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
        continue;
      }
      return response;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      await new Promise((r) => setTimeout(r, Math.pow(2, attempt) * 1000));
    }
  }
}
```

***

## Troubleshooting

| Problem                      | Cause                           | Fix                                                                          |
| ---------------------------- | ------------------------------- | ---------------------------------------------------------------------------- |
| `401 Unauthorized`           | Missing or invalid auth headers | Verify `X-User-Anon-Id` is set correctly. For Public API, check `X-API-Key`. |
| `403 Forbidden`              | Origin not in allowed domains   | Add your domain to the allowed origins list in admin interface > Channels.   |
| SSE stream disconnects early | Proxy or load balancer timeout  | Increase proxy timeout to 60+ seconds, or switch to the polling fallback.    |
| `429 Too Many Requests`      | Rate limit exceeded             | Add client-side throttling and retry with exponential backoff.               |
| Empty response body          | Stream not fully consumed       | Make sure you read until `done === true` before closing the connection.      |

***

## Related Documentation

* [Widget Authentication](../security/widget-authentication) — Token setup for frontend apps
* [API Key Authentication](../security/api-key-authentication) — API key management
* [Portal Integration](./portal-integration) — Full web portal alternative
