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

# End Session

> End a chat session and trigger cleanup: billing finalization, memory ingestion, post-call analysis, and webhooks.

## Overview

Explicitly ends a chat session and triggers all cleanup operations. Always call this endpoint when the user closes the chat or navigates away.

**Authentication:** `Authorization: Bearer {session_token}`

**Important:** Sessions auto-expire after 30 minutes of inactivity, but explicit ending ensures proper cleanup.

***

## Request

**Path Parameters:**

| Parameter         | Type   | Required | Description                             |
| ----------------- | ------ | -------- | --------------------------------------- |
| `conversation_id` | string | ✅        | Session ID from create session response |

**Headers:**

```
Authorization: Bearer {session_token}
```

**Body:** None

***

## Response

**Status:** `200 OK`

```json theme={null}
{
  "success": true,
  "message": "Session ended successfully"
}
```

***

## What Happens on End

When you call `/end`, the following cleanup operations run:

### 1. Billing Finalization

```python theme={null}
# End billing session
await end_billing_session(
    conversation_id,
    user_message_count=session.user_message_count,
    total_message_count=session.total_message_count,
)

# Calculate session cost
session_cost = await get_session_cost(conversation_id)

# Consume credits from organization balance
await consume_credits(
    organization_id=organization_id,
    session_cost=session_cost,
    session_id=billing_session_id
)
```

**Result:** Credits consumed based on actual usage

### 2. Memory Ingestion (Graphiti)

```python theme={null}
# Get all messages
messages = await get_messages(conversation_id, limit=1000)

# Format for Graphiti
graphiti_messages = [
    {"role": msg["role"], "content": msg["content"]}
    for msg in messages
]

# Ingest into knowledge graph
result = await ingest_conversation_async(
    session_id=conversation_id,
    messages=graphiti_messages,
    agent_id=agent_id,
    org_id=organization_id,
    user_id=memory_id  # From user_identifier
)
```

**Result:** Conversation memory available for future sessions

**Timeout:** 25 seconds (fire-and-forget)

### 3. Post-Call Analysis

```python theme={null}
# Trigger analysis (fire-and-forget)
asyncio.create_task(
    trigger_post_call_analysis(conversation_id, agent_id, user_id)
)
```

**Result:** QA analysis if configured on agent

### 4. Webhook Notification

```python theme={null}
# Call agent webhook if configured
if agent_config.webhook_url:
    await call_agent_webhook(
        conversation_id,
        agent_config,
        user_id,
        event_type="chat"
    )
```

**Webhook Payload:**

```json theme={null}
{
  "event": "chat.ended",
  "timestamp": "2024-01-15T10:35:00Z",
  "data": {
    "conversation_id": "chat_abc123",
    "agent_id": "agent_xyz",
    "agent_name": "Support Bot",
    "message_count": 20,
    "input_tokens": 150,
    "output_tokens": 300,
    "total_cost": 0.0045
  }
}
```

**Timeout:** 7 seconds

### 5. Session Removal

```python theme={null}
# Remove from in-memory store
removed = await session_store.end_session(conversation_id)
```

**Result:** Session removed from memory, conversation input cleared

***

## Error Responses

### 401 Unauthorized

```json theme={null}
{
  "detail": "Invalid token"
}
```

### 403 Forbidden

```json theme={null}
{
  "detail": "Conversation ID does not match session token"
}
```

### 500 Server Error

```json theme={null}
{
  "detail": "Failed to end session"
}
```

***

## Examples

### cURL

```bash theme={null}
curl -X POST "https://api.talkifai.dev/v1/chat/sessions/chat_abc123/end" \
  -H "Authorization: Bearer eyJhbGc..."
```

### JavaScript (Fetch)

```javascript theme={null}
async function endSession(conversationId, sessionToken) {
  try {
    const response = await fetch(
      `https://api.talkifai.dev/v1/chat/sessions/${conversationId}/end`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${sessionToken}`
        }
      }
    );

    if (!response.ok) {
      throw new Error('Failed to end session');
    }

    console.log('Session ended successfully');
  } catch (error) {
    console.error('Error ending session:', error);
  }
}
```

### React (Cleanup on Unmount)

```tsx theme={null}
useEffect(() => {
  return () => {
    // Cleanup when component unmounts
    if (sessionTokenRef.current && conversationIdRef.current) {
      fetch(
        `/v1/chat/sessions/${conversationIdRef.current}/end`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${sessionTokenRef.current}`
          }
        }
      ).catch(err => console.error('Cleanup error:', err));
    }
  };
}, []);
```

### React (With keepalive for Tab Close)

```tsx theme={null}
async function endSession(conversationId, token) {
  await fetch(`/v1/chat/sessions/${conversationId}/end`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`
    },
    keepalive: true // Critical: survives tab close
  });
}
```

### beforeunload Handler

```javascript theme={null}
window.addEventListener('beforeunload', () => {
  if (sessionToken && conversationId) {
    fetch(`/v1/chat/sessions/${conversationId}/end`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${sessionToken}`
      },
      keepalive: true // Ensures request completes
    });
  }
});
```

### Next.js (Route Handler)

```typescript theme={null}
// app/api/chat/end/route.ts
export async function POST(request: Request) {
  const { conversationId, sessionToken } = await request.json();

  try {
    const response = await fetch(
      `${process.env.TALKIFAI_API_URL}/v1/chat/sessions/${conversationId}/end`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${sessionToken}`
        }
      }
    );

    const data = await response.json();

    if (!response.ok) {
      return Response.json(data, { status: response.status });
    }

    return Response.json(data);
  } catch (error) {
    return Response.json(
      { error: 'Failed to end session' },
      { status: 500 }
    );
  }
}
```

***

## When to Call /end

### ✅ Always Call When:

1. **User Closes Chat Widget**
   ```javascript theme={null}
   onCloseChatWidget = () => {
     endSession(conversationId, token);
     hideWidget();
   }
   ```

2. **User Navigates Away**
   ```javascript theme={null}
   window.addEventListener('beforeunload', handleCleanup);
   ```

3. **Agent Calls end\_chat Tool**
   ```javascript theme={null}
   if (eventType === 'stream_end' && data.end_session) {
     await endSession(conversationId, token);
   }
   ```

4. **Session Timeout (Idle)**
   * Handled automatically by server
   * But explicit is better than implicit

### ❌ Don't Call When:

1. **Session Already Expired**
   * Token invalid, will return 401
   * No cleanup needed

2. **Mid-Conversation**
   * User is still chatting
   * Wait for natural end

***

## Auto-Expiry vs Explicit End

| Aspect          | Auto-Expiry (30 min)    | Explicit /end |
| --------------- | ----------------------- | ------------- |
| **Trigger**     | Inactivity timeout      | User action   |
| **Billing**     | Finalized               | Finalized     |
| **Memory**      | Ingested                | Ingested      |
| **Analysis**    | Triggered               | Triggered     |
| **Webhook**     | Fired                   | Fired         |
| **Reliability** | Depends on cleanup loop | Immediate     |

<Warning>
  **Always call `/end` explicitly.** Auto-expiry relies on background cleanup loops that may have delays. Explicit ending ensures immediate cleanup.
</Warning>

***

## Best Practices

### 1. Use keepalive

```javascript theme={null}
// ✅ Good
fetch('/end', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  keepalive: true // Survives tab close
});

// ❌ Bad (may be cancelled by browser)
fetch('/end', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` }
});
```

### 2. Handle Errors Gracefully

```javascript theme={null}
try {
  await endSession(conversationId, token);
} catch (error) {
  // Log but don't show to user
  console.error('Cleanup error (non-fatal):', error);
  // Session will auto-expire anyway
}
```

### 3. Clear Local State

```javascript theme={null}
async function endSession(conversationId, token) {
  await fetch(`/v1/chat/sessions/${conversationId}/end`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}` },
    keepalive: true
  });

  // Clear local state
  sessionStorage.removeItem('conversation_id');
  sessionStorage.removeItem('session_token');
  setMessages([]);
}
```

### 4. Debounce Rapid Calls

```javascript theme={null}
const endSessionRef = useRef(null);

const endSessionWithDebounce = useCallback((conversationId, token) => {
  if (endSessionRef.current) return; // Already ending

  endSessionRef.current = setTimeout(async () => {
    await endSession(conversationId, token);
    endSessionRef.current = null;
  }, 1000);
}, []);
```

***

## Related Endpoints

* [Create Session](/api-reference/chat/create-session) — Start new session
* [Send Message](/api-reference/chat/send-message) — Send messages with SSE
* [Get History](/api-reference/chat/get-history) — Retrieve message history

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Create New Session" icon="plus">
    Start a fresh conversation with a new session.
  </Card>

  <Card title="View Analytics" icon="chart">
    Check session costs and usage in dashboard.
  </Card>
</CardGroup>
