> For the complete documentation index, see [llms.txt](https://gdplabs.gitbook.io/meemo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gdplabs.gitbook.io/meemo/external-api/external-api-documentation.md).

# External API Documentation

This documentation explains how external systems can authenticate and access Meemo meeting data via the External API.

## Overview

The External API uses **OAuth2 Client Credentials** flow for authentication. External systems receive a `client_id` and `client_secret`, exchange them for an access token, and use that token to access meeting data.

## Prerequisites

1. An **ExternalApplication** must be created by a Meemo administrator
2. The ExternalApplication links your OAuth2 credentials to one or more organizations
3. You can access meetings belonging to all linked organizations
4. Optionally, you can filter requests to a specific organization using the `organization_id` parameter

***

## Multi-Organization Support

External applications can be configured to access meetings from multiple organizations. This allows a single integration to work across different organizations without requiring separate credentials.

### How It Works

1. **Single Authentication**: One set of OAuth2 credentials works for all linked organizations
2. **Default Behavior**: API calls without `organization_id` return data from ALL accessible organizations
3. **Filtered Access**: Use the `organization_id` query parameter to limit results to a specific organization
4. **Backward Compatible**: Existing integrations with a single organization continue to work without changes

### Example Use Cases

* **Multi-Tenant Platforms**: SaaS platforms serving multiple companies can use one integration
* **Enterprise Deployments**: Large organizations with multiple divisions can centralize access
* **Partner Integrations**: Third-party tools can access data across all client organizations

***

## Step 1: Obtain Credentials

Contact your Meemo administrator to create an ExternalApplication for your integration. You will receive:

* `client_id` - Your application identifier
* `client_secret` - Your secret key (keep this secure!)

***

## Step 2: Get Access Token

Exchange your credentials for an access token using the OAuth2 token endpoint.

### Request

```http
POST /api/auth/token/
Content-Type: application/x-www-form-urlencoded
```

### Parameters

| Parameter       | Value                |
| --------------- | -------------------- |
| `grant_type`    | `client_credentials` |
| `client_id`     | Your client ID       |
| `client_secret` | Your client secret   |

### Example (cURL)

```bash
curl -X POST https://your-meemo-instance.com/api/auth/token/ \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
```

### Response

```json
{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 10800,
  "scope": "read write"
}
```

***

## Step 3: Revoke Access Token

If you need to revoke an access token before it expires, use the revocation endpoint.

### Request

```http
POST /api/auth/revoke-token/
Content-Type: application/x-www-form-urlencoded
```

### Parameters

| Parameter       | Value               |
| --------------- | ------------------- |
| `client_id`     | Your client ID      |
| `client_secret` | Your client secret  |
| `token`         | The token to revoke |

### Example (cURL)

```bash
curl -X POST https://your-meemo-instance.com/api/auth/revoke-token/ \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "token=YOUR_ACCESS_TOKEN"
```

***

## Step 4: Make Authenticated Requests

Include the access token in the `Authorization` header for all API requests.

### Header Format

```
Authorization: Bearer YOUR_ACCESS_TOKEN
```

***

## API Endpoints Reference

### 1. List Meetings

Returns a paginated list of meetings for your accessible organization(s). Meetings are ordered by start time descending (newest first).

**Request**

```http
GET /api/v1/external/meeting/
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**Query Parameters**

| Parameter          | Type    | Description                                                                                                           |
| ------------------ | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `organization_id`  | integer | **Optional**. Filter to a specific organization. If not provided, returns meetings from all accessible organizations. |
| `title`            | string  | Filter by title (case-insensitive, contains match)                                                                    |
| `participants`     | string  | Filter by participant user IDs (comma-separated, matches ANY)                                                         |
| `created_after`    | date    | Filter meetings created on or after this date (YYYY-MM-DD)                                                            |
| `created_before`   | date    | Filter meetings created on or before this date (YYYY-MM-DD)                                                           |
| `summary_complete` | boolean | Filter by summary completion status (`true`/`false`)                                                                  |
| `from_calendar`    | boolean | Filter by meeting source. `true`=calendar integration, `false`=manually created                                       |
| `page`             | integer | **Optional**. Page number for pagination.                                                                             |
| `size`             | integer | **Optional**. Number of results per page (default: 10).                                                               |

**Example Requests**

```bash

# Get meetings from all accessible organizations
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get meetings from a specific organization only
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/?organization_id=5" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Filter by title and date (across all organizations)
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/?title=standup&created_after=2024-01-01&summary_complete=true" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get second page of results with page size 20
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/?page=2&size=20" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response**

```json
{
  "count": 45,
  "next": "https://your-meemo-instance.com/api/v1/external/meeting/?page=2",
  "previous": null,
  "results": [
    {
      "id": 123,
      "title": "Daily Standup",
      "start_time": "2024-01-15T09:00:00Z",
      "end_time": "2024-01-15T09:30:00Z",
      "created_at": "2024-01-15T08:55:00Z",
      "status": "past",
      "summary_complete": true,
      "host_name": "John Doe",
      "calendar_event": null
    },
    {
      "id": 124,
      "title": "Weekly Standup Review",
      "start_time": "2024-01-22T09:00:00Z",
      "end_time": "2024-01-22T09:45:00Z",
      "created_at": "2024-01-22T08:50:00Z",
      "status": "past",
      "summary_complete": true,
      "host_name": "Jane Smith",
      "calendar_event": {
        "event_id": "abc123def456",
        "summary": "Weekly Standup Review",
        "organizer_email": "admin@example.com",
        "attendees": [
          {
            "email": "john.doe@example.com",
            "display_name": "John Doe",
            "response_status": "accepted"
          }
        ],
        "meeting_link": "https://meet.google.com/xyz-abcd-efg",
        "start_time": "2024-01-22T09:00:00Z",
        "end_time": "2024-01-22T09:45:00Z"
      }
    }
  ]
}
```

**Response Fields**

| Field                        | Type        | Description                                                          |
| ---------------------------- | ----------- | -------------------------------------------------------------------- |
| `count`                      | integer     | Total number of meetings across all pages                            |
| `next`                       | string/null | URL to the next page of results                                      |
| `previous`                   | string/null | URL to the previous page of results                                  |
| `results`                    | array       | List of meeting objects for the current page                         |
| `results[].id`               | integer     | Meeting ID                                                           |
| `results[].title`            | string      | Meeting title                                                        |
| `results[].start_time`       | datetime    | Meeting start time (ISO 8601)                                        |
| `results[].end_time`         | datetime    | Meeting end time (ISO 8601)                                          |
| `results[].created_at`       | datetime    | Meeting creation time (ISO 8601)                                     |
| `results[].status`           | string      | Meeting status (`upcoming`, `ongoing`, `recording`, `past`)          |
| `results[].summary_complete` | boolean     | Whether summary generation is complete                               |
| `results[].host_name`        | string      | Full name of the meeting host                                        |
| `results[].calendar_event`   | object/null | Calendar event data if meeting was created from calendar integration |

**Calendar Event Fields** (when `calendar_event` is not null)

| Field                         | Type        | Description                                   |
| ----------------------------- | ----------- | --------------------------------------------- |
| `event_id`                    | string      | Google Calendar event ID                      |
| `summary`                     | string      | Event summary/title from calendar             |
| `organizer_email`             | string/null | Email of the meeting organizer                |
| `attendees`                   | array       | List of attendees from calendar               |
| `attendees[].email`           | string      | Attendee email                                |
| `attendees[].display_name`    | string/null | Attendee display name                         |
| `attendees[].response_status` | string      | Response status (accepted/declined/tentative) |
| `meeting_link`                | string/null | Video conference link (Google Meet, etc.)     |
| `start_time`                  | datetime    | Event start time from calendar                |
| `end_time`                    | datetime    | Event end time from calendar                  |

***

### 2. Get Meeting Details

Returns detailed information about a specific meeting. The meeting must belong to one of your accessible organizations.

**Request**

```http
GET /api/v1/external/meeting/{id}/
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**Path Parameters**

| Parameter | Type    | Description |
| --------- | ------- | ----------- |
| `id`      | integer | Meeting ID  |

**Example Request**

```bash
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/123/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

> **Note**: You can only access meetings that belong to organizations linked to your ExternalApplication.

**Response**

```json
{
  "id": 123,
  "title": "Q1 Planning Meeting",
  "start_time": "2024-01-15T09:00:00Z",
  "end_time": "2024-01-15T11:00:00Z",
  "created_at": "2024-01-14T16:30:00Z",
  "location": "Conference Room A",
  "status": "past",
  "summary_complete": true,
  "host": {
    "id": 1,
    "name": "John Doe",
    "email": "john.doe@example.com"
  },
  "language": "id",
  "keywords": ["planning", "q1", "budget"],
  "participant_count": 5,
  "duration_seconds": 7200.0,
  "num_cluster": 5,
  "calendar_event": {
    "event_id": "abc123xyz789",
    "summary": "Q1 Planning Meeting",
    "organizer_email": "admin@example.com",
    "attendees": [
      {
        "email": "john.doe@example.com",
        "display_name": "John Doe",
        "response_status": "accepted"
      }
    ],
    "meeting_link": "https://meet.google.com/xyz-abcd-efg",
    "start_time": "2024-01-15T09:00:00Z",
    "end_time": "2024-01-15T11:00:00Z"
  }
}
```

**Response Fields**

| Field               | Type        | Description                                                 |
| ------------------- | ----------- | ----------------------------------------------------------- |
| `id`                | integer     | Meeting ID                                                  |
| `title`             | string      | Meeting title                                               |
| `start_time`        | datetime    | Meeting start time (ISO 8601)                               |
| `end_time`          | datetime    | Meeting end time (ISO 8601)                                 |
| `created_at`        | datetime    | Meeting creation time (ISO 8601)                            |
| `location`          | string      | Meeting location                                            |
| `status`            | string      | Meeting status (`upcoming`, `ongoing`, `recording`, `past`) |
| `summary_complete`  | boolean     | Whether summary generation is complete                      |
| `host`              | object      | Host user information (`id`, `name`, `email`)               |
| `language`          | string      | Meeting language code (e.g., `id`, `en`)                    |
| `keywords`          | array       | List of keyword tags                                        |
| `participant_count` | integer     | Number of participants                                      |
| `duration_seconds`  | float       | Meeting duration in seconds (based on start/end time)       |
| `num_cluster`       | integer     | Number of speaker clusters detected during diarization      |
| `calendar_event`    | object/null | Calendar event data if from calendar integration            |

**Calendar Event Fields** (when `calendar_event` is not null)

| Field                         | Type        | Description                                               |
| ----------------------------- | ----------- | --------------------------------------------------------- |
| `event_id`                    | string      | Google Calendar event ID                                  |
| `summary`                     | string      | Event summary/title from calendar                         |
| `organizer_email`             | string/null | Email of the meeting organizer                            |
| `attendees`                   | array       | List of attendees from calendar invite                    |
| `attendees[].email`           | string      | Attendee email address                                    |
| `attendees[].display_name`    | string/null | Attendee display name                                     |
| `attendees[].response_status` | string      | Response status (accepted/declined/tentative/needsAction) |
| `meeting_link`                | string/null | Video conference link (Google Meet URL)                   |
| `start_time`                  | datetime    | Event start time from calendar (ISO 8601)                 |
| `end_time`                    | datetime    | Event end time from calendar (ISO 8601)                   |

> **Note**: The `calendar_event` field is only populated for meetings created through calendar integration with automatic bot joining. Manually created meetings will have `calendar_event: null`.

***

### 3. Get Meeting Transcript

Returns the meeting transcript as a JSON array of segments, ordered by start time. The meeting must belong to one of your accessible organizations.

**Request**

```http
GET /api/v1/external/meeting/{id}/transcript/
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**Path Parameters**

| Parameter | Type    | Description |
| --------- | ------- | ----------- |
| `id`      | integer | Meeting ID  |

**Example Request**

```bash
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/123/transcript/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response**

```json
{
  "meeting_id": 123,
  "title": "Q1 Planning Meeting",
  "total_segments": 45,
  "transcripts": [
    {
      "speaker": "John Doe",
      "text": "Selamat pagi semua. Mari kita mulai rapat hari ini.",
      "start_time": 0.0,
      "end_time": 4.5
    },
    {
      "speaker": "Jane Smith",
      "text": "Terima kasih Pak John. Saya akan memulai dengan laporan Q4.",
      "start_time": 5.0,
      "end_time": 9.2
    },
    {
      "speaker": "Unknown",
      "text": "Maaf, bisa diulangi?",
      "start_time": 10.0,
      "end_time": 11.5
    }
  ]
}
```

**Response Fields**

| Field                      | Type    | Description                                 |
| -------------------------- | ------- | ------------------------------------------- |
| `meeting_id`               | integer | Meeting ID                                  |
| `title`                    | string  | Meeting title                               |
| `total_segments`           | integer | Total number of transcript segments         |
| `transcripts`              | array   | Array of transcript segments                |
| `transcripts[].speaker`    | string  | Speaker name or "Unknown" if unidentified   |
| `transcripts[].text`       | string  | Transcript text (revised > formatted > raw) |
| `transcripts[].start_time` | float   | Segment start time in seconds               |
| `transcripts[].end_time`   | float   | Segment end time in seconds                 |

> **Note**: Confidential segments are excluded from the response.

***

### 4. Get Meeting Summary

Returns the meeting summary, notes, and keywords. The meeting must belong to one of your accessible organizations.

**Request**

```http
GET /api/v1/external/meeting/{id}/summary/
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**Path Parameters**

| Parameter | Type    | Description |
| --------- | ------- | ----------- |
| `id`      | integer | Meeting ID  |

**Example Request**

```bash
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/123/summary/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response (New Markdown Format)**

```json
{
  "meeting_id": 123,
  "title": "Q1 Planning Meeting",
  "summary": "## Meeting Purpose\nThe meeting discussed the technical requirements...\n\n## Executive Summary\n- Decision A was made\n- Point B was discussed\n...",
  "notes": "Additional meeting notes here",
  "keywords": ["planning", "q1", "budget"]
}
```

**Response Fields**

| Field        | Type               | Description                                                           |
| ------------ | ------------------ | --------------------------------------------------------------------- |
| `meeting_id` | integer            | Meeting ID                                                            |
| `title`      | string             | Meeting title                                                         |
| `summary`    | string/object/null | Meeting summary content. Now primarily returns a **Markdown string**. |
| `notes`      | string             | Meeting notes text                                                    |
| `keywords`   | array              | List of keyword tags                                                  |

> **Important Note on Summary Format**:
>
> * **Markdown (New)**: The `summary` field now primarily returns a Markdown-formatted string. This is the preferred format for all new meetings.
> * **Structured JSON (Legacy)**: Older meetings may still return a structured JSON object (e.g., `{"ringkasan": "...", "poin_penting": [...]}`). This format is **deprecated** and will eventually be phased out as meetings are re-summarized into the new Markdown format.
> * Integrations should be prepared to handle both a `string` (Markdown) and an `object` (Legacy JSON) during the transition period.

***

### 5. Get Meeting Participants

Returns the list of meeting participants with their details. The meeting must belong to one of your accessible organizations.

**Request**

```http
GET /api/v1/external/meeting/{id}/participants/
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**Path Parameters**

| Parameter | Type    | Description |
| --------- | ------- | ----------- |
| `id`      | integer | Meeting ID  |

**Example Request**

```bash
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/123/participants/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response**

```json
{
  "meeting_id": 123,
  "title": "Q1 Planning Meeting",
  "total_participants": 5,
  "participants": [
    {
      "id": 1,
      "name": "John Doe",
      "email": "john.doe@example.com",
      "position": "Director",
      "department": "Engineering"
    },
    {
      "id": 2,
      "name": "Jane Smith",
      "email": "jane.smith@example.com",
      "position": "Manager",
      "department": "Finance"
    },
    {
      "id": null,
      "name": "External Guest",
      "email": "guest@external.com",
      "position": "",
      "department": ""
    }
  ]
}
```

**Response Fields**

| Field                       | Type         | Description                        |
| --------------------------- | ------------ | ---------------------------------- |
| `meeting_id`                | integer      | Meeting ID                         |
| `title`                     | string       | Meeting title                      |
| `total_participants`        | integer      | Total number of participants       |
| `participants`              | array        | Array of participant objects       |
| `participants[].id`         | integer/null | User ID (null for external guests) |
| `participants[].name`       | string       | Participant display name           |
| `participants[].email`      | string/null  | Participant email                  |
| `participants[].position`   | string       | Position within organization       |
| `participants[].department` | string       | Department within organization     |

***

### 6. Get Meeting Recording

Returns the meeting recording URL, duration, and format. The meeting must belong to one of your accessible organizations.

**Request**

```http
GET /api/v1/external/meeting/{id}/recording/
Authorization: Bearer YOUR_ACCESS_TOKEN
```

**Path Parameters**

| Parameter | Type    | Description |
| --------- | ------- | ----------- |
| `id`      | integer | Meeting ID  |

**Example Request**

```bash
curl -X GET "https://your-meemo-instance.com/api/v1/external/meeting/123/recording/" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

**Response**

```json
{
  "meeting_id": 123,
  "title": "Q1 Planning Meeting",
  "recording_url": "/media/meeting_records/123/recording.mp3",
  "duration": 7200.0,
  "format": "mp3"
}
```

**Response (No Recording)**

```json
{
  "meeting_id": 123,
  "title": "Q1 Planning Meeting",
  "recording_url": null,
  "duration": 0.0,
  "format": ""
}
```

**Response Fields**

| Field           | Type        | Description                                           |
| --------------- | ----------- | ----------------------------------------------------- |
| `meeting_id`    | integer     | Meeting ID                                            |
| `title`         | string      | Meeting title                                         |
| `recording_url` | string/null | Relative URL to recording file (null if no recording) |
| `duration`      | float       | Recording duration in seconds                         |
| `format`        | string      | Audio format ("mp3", "wav", or empty)                 |

> **Note**: MP3 format is preferred for external consumption when available.

***

## Token Expiration & Refresh

* Access tokens expire after **3 hours** (10800 seconds) by default
* When your token expires, request a new one using Step 2
* Store and reuse tokens until they expire to minimize token requests

### Handling Expiration

```python
import requests
from datetime import datetime, timedelta

class MeemoClient:
    def __init__(self, client_id, client_secret, base_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.base_url = base_url
        self.access_token = None
        self.token_expires_at = None

    def get_token(self):
        if self.access_token and self.token_expires_at > datetime.now():
            return self.access_token

        response = requests.post(
            f"{self.base_url}/api/auth/token/",
            data={
                "grant_type": "client_credentials",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
            }
        )
        
        data = response.json()
        self.access_token = data["access_token"]
        self.token_expires_at = datetime.now() + timedelta(seconds=data["expires_in"] - 60)
        
        return self.access_token

    def get_meetings(self, organization_id=None, **filters):
        """Get meetings, optionally filtered by organization."""
        token = self.get_token()
        params = filters.copy()
        if organization_id:
            params['organization_id'] = organization_id
        
        response = requests.get(
            f"{self.base_url}/api/v1/external/meeting/",
            headers={"Authorization": f"Bearer {token}"},
            params=params
        )
        return response.json()

    def get_meeting_detail(self, meeting_id):
        token = self.get_token()
        response = requests.get(
            f"{self.base_url}/api/v1/external/meeting/{meeting_id}/",
            headers={"Authorization": f"Bearer {token}"}
        )
        return response.json()

    def get_transcript(self, meeting_id):
        token = self.get_token()
        response = requests.get(
            f"{self.base_url}/api/v1/external/meeting/{meeting_id}/transcript/",
            headers={"Authorization": f"Bearer {token}"}
        )
        return response.json()
```

***

## Error Responses

| Status Code | Description                                                 |
| ----------- | ----------------------------------------------------------- |
| 400         | Invalid request parameters (e.g., invalid organization\_id) |
| 401         | Invalid or expired token                                    |
| 403         | Token valid but no access to resource                       |
| 404         | Meeting not found or not in your accessible organizations   |

### Example Error Responses

**401 Unauthorized**

```json
{
  "detail": "Invalid or inactive external application credentials."
}
```

**400 Bad Request**

```json
{
  "organization_id": "Organization 123 is not accessible to this external application."
}
```

**400 Bad Request (Invalid Format)**

```json
{
  "organization_id": "Invalid organization_id format. Must be an integer."
}
```

**404 Not Found**

```json
{
  "detail": "Meeting not found"
}
```

***

## Security Best Practices

1. **Keep credentials secure** - Never expose `client_secret` in client-side code
2. **Use HTTPS** - Always use HTTPS for API requests
3. **Rotate credentials** - Periodically request new credentials from your administrator
4. **Minimal scope** - Only request data you need
5. **Token storage** - Store tokens securely, never in plain text logs

***

## Support

For issues with API access or to request credentials, contact your Meemo administrator.
