# API Documentation

The **AI Song Detector API** lets you check whether an audio file contains **AI-generated music**.

The submitted audio file must contain **music**, ideally a **single song**. Files may be uploaded **directly** or retrieved from a provided **URL**. Please avoid files with UGC compilations or other mixed content. See more on [Overview](https://docs.pex.com/ai-song-detector/overview) page.

The model produces **one prediction for the entire file**.

To use the API:

1. Request an **access token** via OAuth 2.0 Client Credentials.
2. Submit an **audio file** to the detection endpoint, either by uploading it directly or by providing a URL to the file. All common audio file formats are supported.
3. Receive a **JSON result** describing whether the file contains AI-generated music.

***

## **1. Authentication**

The API uses **OAuth 2.0 Client Credentials**. You will need a `CLIENT_ID` and `CLIENT_SECRET`.

The generated access token must be included in every request to the detection endpoint.

The access token is valid for 2 hours. Clients should reuse the same token for multiple requests until it expires, rather than requesting a new token for each call. When the token expires, request a new one using the same client credentials.

#### **Token Endpoint**

**POST**

`https://api.ae.pex.com/oauth2/token`

* Auth: **HTTP Basic Auth**
* Body: `grant_type=client_credentials`

#### **Examples**

{% tabs %}
{% tab title="Python" %}

```python
import requests

token_url = "https://api.ae.pex.com/oauth2/token"
auth = ("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")

token_resp = requests.post(token_url, data={"grant_type": "client_credentials"}, auth=auth)
token_resp.raise_for_status()
access_token = token_resp.json()["access_token"]
```

{% endtab %}

{% tab title="Bash" %}

```bash
CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_SECRET="YOUR_CLIENT_SECRET"

TOKEN_RESPONSE=$(curl -s \
  -u "${CLIENT_ID}:${CLIENT_SECRET}" \
  -d "grant_type=client_credentials" \
  "https://api.ae.pex.com/oauth2/token")

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
echo "Token: $ACCESS_TOKEN"
```

{% endtab %}
{% endtabs %}

***

## **2. AI Song Detector Endpoints**

Submit an audio file to the detection endpoint, either by uploading it directly (see [#id-2a.-ai-song-detector-file-upload-endpoint](#id-2a.-ai-song-detector-file-upload-endpoint "mention")) or by providing a URL to the file (see [#id-2b.-ai-song-detector-url-endpoint](#id-2b.-ai-song-detector-url-endpoint "mention")).

#### **File constraints**

* **Supported formats:** Most common audio formats (MP3, MP4/M4A, WAV, FLAC, AAC, OGG, WEBM, etc.).
* **Maximum file size:** **100 MB.**
* **Minimum duration:** **30 seconds.**
* **Maximum duration:** **15 minutes.**
* Stereo, mono, and variable bitrates are supported.

Files shorter or longer than the limits will return `too_short` or `too_long` in the response status. Files containing a lot of silence or non-music parts may return `not_enough_music`.

{% hint style="info" %}
The submitted audio file, whether uploaded directly or retrieved from a URL, is processed and immediately discarded. It is never stored on our servers.
{% endhint %}

***

### 2A. AI Song Detector - File Upload Endpoint

This endpoint receives a file and returns AI music detector results.

**POST**

`https://api.ae.pex.com/v1/ai-detector/detect`

#### **Requirements**

* **Authorization:** `Bearer <access_token>`
* **Content-Type:** `multipart/form-data`
* Body: `file=@your_audio_file`

#### **Examples**

{% tabs %}
{% tab title="Python" %}

```python
import requests

url = "https://api.ae.pex.com/v1/ai-detector/detect"
headers = {"Authorization": f"Bearer {access_token}"}

with open("song.mp3", "rb") as f:
    resp = requests.post(url, headers=headers, files={"file": f})

resp.raise_for_status()
print(resp.json())
```

{% endtab %}

{% tab title="Bash" %}

```bash
curl -s \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -F "file=@song.mp3" \
  "https://api.ae.pex.com/v1/ai-detector/detect"
```

{% endtab %}
{% endtabs %}

***

### 2B. AI Song Detector - URL Endpoint

This endpoint receives a URL from which an audio file is retrieved and analyzed.

The URL must be publicly accessible without authentication and must allow direct file download. If the URL cannot be opened or accessed, the status `not_found` is returned.

**POST**

`https://api.ae.pex.com/v1/ai-detector/detect-url`

#### **Requirements**

* **Authorization:** `Bearer <access_token>`
* **Content-Type:** `application/x-www-form-urlencoded`
* Body: `url=your_audio_file_url`

#### **Examples**

{% tabs %}
{% tab title="Python" %}

```python
import requests

url = "https://api.ae.pex.com/v1/ai-detector/detect-url"
headers = {"Authorization": f"Bearer {access_token}"}

resp = requests.post(url, headers=headers, data={"url": "https://server/path/song.mp3"})

resp.raise_for_status()
print(resp.json())
```

{% endtab %}

{% tab title="Bash" %}

```bash
curl -s \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -d "url=https://server/path/song.mp3" \
  "https://api.ae.pex.com/v1/ai-detector/detect-url"
```

{% endtab %}
{% endtabs %}

***

## **3. Response Format**

A successful request (HTTP 200 OK) with status `ok` returns a JSON object such as:

```json
{
  "request_id": 806957142976735233,
  "status": "ok",
  "message": "ok",
  "is_ai": true,
  "ai_score": 0.9992,
  "predicted_model": "suno",
  "predicted_model_score": 0.9723
}
```

### **Response Fields**

<table><thead><tr><th width="220.5">Field</th><th width="88.5">Type</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>request_id</code></strong></td><td>integer</td><td>Unique ID for this request.</td></tr><tr><td><strong><code>status</code></strong></td><td>string</td><td>Processing status (see full list below).</td></tr><tr><td><strong><code>message</code></strong></td><td>string</td><td>Human-readable explanation of the status.</td></tr><tr><td><strong><code>is_ai</code></strong></td><td>boolean</td><td><p>Whether the model classifies the song as AI-generated.</p><p>Present only if <code>status</code> is <code>ok</code>.</p></td></tr><tr><td><strong><code>ai_score</code></strong></td><td>float</td><td><p>Score [0–1] representing how strongly the model considers the audio to be AI-generated.</p><p>This is not a calibrated probability and should not be used to override or post-filter the <code>is_ai</code> classification. </p><p>Suitable for sorting or QA across multiple results. Score ranges may change between model releases.</p><p>Present only if <code>status</code> is <code>ok</code>.</p></td></tr><tr><td><strong><code>predicted_model</code></strong></td><td>string</td><td><p>Name of the predicted model, e.g. "suno", "udio", etc.</p><p>Present only if <code>status</code> is <code>ok</code>.<br>May be <code>null</code> even when <code>is_ai</code> is <code>true</code>, if the detector is not sufficiently confident about the specific AI generation platform.</p></td></tr><tr><td><strong><code>predicted_model_score</code></strong></td><td>float</td><td>Score of the predicted model.<br>Present only if <code>status</code> is <code>ok</code> and <code>predicted_model</code> is set.</td></tr></tbody></table>

***

## **4. Status Codes**

<table><thead><tr><th width="167">status</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>ok</code></strong></td><td>Processing completed successfully. Fields <code>is_ai</code> and <code>ai_score</code> are provided.</td></tr><tr><td><strong><code>invalid_file</code></strong></td><td>The file is not recognized as a valid media file, is corrupted, or the container cannot be parsed.</td></tr><tr><td><strong><code>no_audio</code></strong></td><td>The media file is valid, but contains no extractable audio track.</td></tr><tr><td><strong><code>too_short</code></strong></td><td>Audio is shorter than <strong>10 seconds</strong>.</td></tr><tr><td><strong><code>too_long</code></strong></td><td>Audio is longer than <strong>15 minutes</strong>.</td></tr><tr><td><strong><code>not_enough_music</code></strong></td><td>Audio exists, but does not contain enough <strong>music content</strong> for reliable classification.</td></tr><tr><td><strong><code>not_found</code></strong></td><td>The provided URL could not be opened or the file could not be retrieved.</td></tr><tr><td><strong><code>error</code></strong></td><td>Other processing error.</td></tr></tbody></table>

***

## **5. HTTP Error Handling**

The API uses standard error responses:

<table><thead><tr><th width="78.5">Status</th><th>Explanation</th></tr></thead><tbody><tr><td><strong>200</strong></td><td>Request processed. The response body contains a <code>status</code> field that may indicate errors (e.g., <code>invalid_file</code>, <code>too_short</code>).</td></tr><tr><td><strong>400</strong></td><td>Invalid request (missing file or URL, incorrect field name, etc.).</td></tr><tr><td><strong>401</strong></td><td>Missing or invalid OAuth token.</td></tr><tr><td><strong>413</strong></td><td>File too large (over 100 MB).</td></tr><tr><td><strong>429</strong></td><td>Too Many Requests - retry with backoff.</td></tr><tr><td><strong>500</strong></td><td>Unexpected server error. You may retry if temporary.</td></tr><tr><td><strong>502</strong></td><td>Bad Gateway. The server may be overloaded - retry with backoff.</td></tr><tr><td><strong>503</strong></td><td>Service temporarily overloaded - retry with backoff.</td></tr></tbody></table>

#### **Example error response**

```json
{
  "status": "error",
  "message": "Service temporarily unavailable. Please retry later."
}
```

***

## 6. Full Example

This code illustrates how to use both steps (Authentication and use of AI Song Detector Endpoint), including error handling.

{% tabs %}
{% tab title="Python" %}

```python
import json
import requests
import time

CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"

TOKEN_URL = "https://api.ae.pex.com/oauth2/token"
DETECT_URL = "https://api.ae.pex.com/v1/ai-detector/detect"


def get_access_token(client_id, client_secret):
    auth = (client_id, client_secret)
    resp = requests.post(TOKEN_URL, data={"grant_type": "client_credentials"}, auth=auth)
    resp.raise_for_status()
    return resp.json()["access_token"]


def detect_ai_song(access_token, file_name):
    headers = {"Authorization": f"Bearer {access_token}"}

    while True:
        # call AI Song Detector using the file upload endpoint
        with open(file_name, "rb") as f:
            resp = requests.post(DETECT_URL, headers=headers, files={"file": f})

        # Return the result
        if resp.status_code == 200:
            return resp.json()

        # Retry on some errors 
        if resp.status_code in [408, 429, 500, 502, 504, 520, 521, 522, 523, 524]:
            # Log the retryable error
            print(f"HTTP status {resp.status_code}, retrying...")

            # Sleep for 30 seconds
            time.sleep(30)

        else:
            # Raise the non-retryable error 
            resp.raise_for_status()


def main():
    # Get the access token
    access_token = get_access_token(CLIENT_ID, CLIENT_SECRET)

    # Call AI Song Detector
    song_path = "/path/to/your/song.mp3"
    result = detect_ai_song(access_token, song_path)
    # Print the result
    print(json.dumps(result, indent=2))
    
    # Call AI Song Detector for another song.
    # This shows that the access_token can be reused (it's valid for 2 hours)
    song_path2 = "/path/to/your/another/song2.mp3"
    result = detect_ai_song(access_token, song_path2)
    # Print the result
    print(json.dumps(result, indent=2))


if __name__ == '__main__':
    main() 
```

{% endtab %}
{% endtabs %}

## **7. Support**

If you encounter problems, please contact your Pex representative and include:

* the `request_id` (if present),
* timestamp,
* HTTP status code,
* the approximate file duration.
