Skip to content

Instantly share code, notes, and snippets.

@tobq
Last active June 12, 2025 04:00
Show Gist options
  • Select an option

  • Save tobq/02c904192e1a76fca5dc6df445fa7726 to your computer and use it in GitHub Desktop.

Select an option

Save tobq/02c904192e1a76fca5dc6df445fa7726 to your computer and use it in GitHub Desktop.

Revisions

  1. tobq revised this gist Jun 12, 2025. 1 changed file with 0 additions and 53 deletions.
    53 changes: 0 additions & 53 deletions API.md
    Original file line number Diff line number Diff line change
    @@ -73,59 +73,6 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    X-API-Key: your-api-key-here
    ```

    #### 3. OAuth2 Authorization Code Flow
    ```http
    # Redirect users to authorization URL
    https://api.twoshot.app/oauth/authorize?client_id=YOUR_CLIENT&response_type=code&scope=read write
    # Exchange code for tokens
    POST /auth/exchange
    Content-Type: application/json
    {
    "code": "authorization_code",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret"
    }
    ```

    **Scopes:**
    - `read` - Access user data and public content
    - `write` - Create and modify user content

    ### Authentication Flow Examples

    #### User Registration & Login
    ```javascript
    // Register new user
    const registerResponse = await fetch('/auth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
    auth0IdToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
    userName: "musicmaker2024",
    firstName: "John",
    lastName: "Doe"
    })
    });

    const tokens = await registerResponse.json();
    // Store tokens.accessToken and tokens.refreshToken
    ```

    #### Token Refresh
    ```javascript
    // Refresh expired token
    const refreshResponse = await fetch('/auth/refresh', {
    method: 'POST',
    headers: {
    'Authorization': `Bearer ${refreshToken}`
    }
    });

    const newTokens = await refreshResponse.json();
    ```

    ---

    ## Project Management
  2. tobq created this gist Jun 12, 2025.
    935 changes: 935 additions & 0 deletions API.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,935 @@
    # TwoShot Platform API Documentation

    ## Overview

    The TwoShot Platform API is a comprehensive REST API for music production, AI-powered audio generation, and sample library management. Built for developers creating music production tools, AI audio applications, and creative platforms.

    **Base URLs:**
    - Production: `https://api.twoshot.app`
    **API Version:** v1

    ---

    ## Use Cases & Integration Scenarios

    ### 🎵 Music Production Platform Integration
    Build DAW plugins, web-based music studios, or mobile music creation apps.

    **Example Flow:**
    1. User creates project → `POST /project`
    2. Import audio samples → `POST /sample/audio`
    3. Build project with tracks/clips → `PUT /project/{id}`
    4. Render final audio → `POST /project/{id}/render`

    ### 🤖 AI Music Generation App
    Create applications that generate music using AI models.

    **Example Flow:**
    1. Browse available models → `GET /model`
    2. Start generation job → `POST /generation`
    3. Poll for completion → `GET /generation/{jobId}`
    4. Download generated audio → `GET /audio/{audioId}/download`

    ### 📚 Sample Library Platform
    Build sample discovery and distribution platforms.

    **Example Flow:**
    1. Browse public samples → `GET /sample`
    2. Preview samples → `GET /sample/{sampleId}/preview/{audioId}`
    3. Apply licensing → `POST /licence/apply`
    4. Download licensed samples → `GET /sample/{sampleId}/download/{audioId}`

    ### 🎬 Content Creator Tools
    Integrate music generation into video editing, podcasting, or streaming tools.

    **Example Flow:**
    1. Generate background music → `POST /generation`
    2. Create video project → `POST /video`
    3. Sync audio with video → `PUT /video/{id}`
    4. Export final content → `GET /video/{id}/download`

    ### 🌐 Social Music Platform
    Build social platforms for music sharing and collaboration.

    **Example Flow:**
    1. User registration → `POST /auth/register`
    2. Share public projects → `PUT /project/{id}` (set public: true)
    3. Follow other users → `POST /user/{userId}/follow`
    4. Discover trending content → `GET /tag/explore`

    ---

    ## Authentication

    ### Authentication Methods

    #### 1. Bearer Token (JWT) - Primary Method
    ```http
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    ```

    #### 2. API Key
    ```http
    X-API-Key: your-api-key-here
    ```

    #### 3. OAuth2 Authorization Code Flow
    ```http
    # Redirect users to authorization URL
    https://api.twoshot.app/oauth/authorize?client_id=YOUR_CLIENT&response_type=code&scope=read write
    # Exchange code for tokens
    POST /auth/exchange
    Content-Type: application/json
    {
    "code": "authorization_code",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret"
    }
    ```

    **Scopes:**
    - `read` - Access user data and public content
    - `write` - Create and modify user content

    ### Authentication Flow Examples

    #### User Registration & Login
    ```javascript
    // Register new user
    const registerResponse = await fetch('/auth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
    auth0IdToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...",
    userName: "musicmaker2024",
    firstName: "John",
    lastName: "Doe"
    })
    });

    const tokens = await registerResponse.json();
    // Store tokens.accessToken and tokens.refreshToken
    ```

    #### Token Refresh
    ```javascript
    // Refresh expired token
    const refreshResponse = await fetch('/auth/refresh', {
    method: 'POST',
    headers: {
    'Authorization': `Bearer ${refreshToken}`
    }
    });

    const newTokens = await refreshResponse.json();
    ```

    ---

    ## Project Management

    Create and manage music production projects with full DAW-like functionality.

    ### Create Project

    **POST** `/project`

    Create a new music project with tracks, clips, and audio sources.

    **Authentication:** Required

    **Request Body:**
    ```json
    {
    "name": "My New Track",
    "version": "1.0.0",
    "tempo": 120.0,
    "grid": 0.25,
    "snapToGrid": true,
    "playHead": {
    "start": 0.0,
    "end": 16.0
    },
    "soloTarget": null,
    "tracks": [
    {
    "id": "550e8400-e29b-41d4-a716-446655440001",
    "name": "Drums",
    "clips": [
    {
    "id": "550e8400-e29b-41d4-a716-446655440002",
    "name": "Kick Pattern",
    "color": { "hue": 0.8, "saturation": 0.7, "lightness": 0.5 },
    "position": 0.0,
    "muted": false,
    "volume": 0.8,
    "source": {
    "audioId": "550e8400-e29b-41d4-a716-446655440003",
    "sampleId": 12345
    },
    "pitchOffset": 0.0,
    "reversed": false,
    "speed": "tempo-sync",
    "cutStart": 0.0,
    "cutEnd": 2.5,
    "pan": 0.0
    }
    ],
    "pan": 0.0,
    "loopEvery": "auto",
    "volume": 0.9,
    "muted": false
    }
    ]
    }
    ```

    **Response:**
    ```json
    {
    "id": "550e8400-e29b-41d4-a716-446655440000"
    }
    ```

    **Example Usage:**
    ```javascript
    const project = await fetch('/project', {
    method: 'POST',
    headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
    },
    body: JSON.stringify({
    name: "My Beat",
    tempo: 140,
    tracks: []
    })
    });

    const { id } = await project.json();
    console.log('Created project:', id);
    ```

    ### Get Projects

    **GET** `/project`

    Retrieve projects with filtering, pagination, and search capabilities.

    **Authentication:** Optional (returns public projects for anonymous users)

    **Query Parameters:**
    - `owner_id` (integer) - Filter by owner ID
    - `public` (boolean) - Filter by public projects only
    - `name` (string) - Search by project name
    - `limit` (integer, max 10, default 10) - Results per page
    - `offset` (integer, default 0) - Pagination offset
    - `minTempo` (number) - Minimum BPM filter
    - `maxTempo` (number) - Maximum BPM filter
    - `minDuration` (number) - Minimum duration in seconds
    - `maxDuration` (number) - Maximum duration in seconds

    **Response:**
    ```json
    [
    {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "owner": {
    "id": 12345,
    "userName": "musicmaker2024",
    "verified": true,
    "featured": false,
    "displayPicture": {
    "id": 67890,
    "url": "https://cdn.twoshot.app/images/profile_67890.jpg"
    },
    "followed": false
    },
    "public": true,
    "name": "Epic House Track",
    "version": "2.1.0",
    "created": "2024-01-15T10:30:00Z",
    "updated": "2024-01-16T14:45:30Z",
    "data": {
    "tempo": 128.0,
    "grid": 0.25,
    "tracks": [...]
    }
    }
    ]
    ```

    **Example Usage:**
    ```javascript
    // Get public projects with tempo filter
    const projects = await fetch('/project?public=true&minTempo=120&maxTempo=140&limit=10')
    .then(r => r.json());

    // Get user's own projects
    const myProjects = await fetch('/project?owner_id=12345', {
    headers: { 'Authorization': `Bearer ${token}` }
    }).then(r => r.json());
    ```

    ### Render Project

    **POST** `/project/{id}/render`

    Render project to audio file.

    **Authentication:** Required

    **Request Body:**
    ```json
    {
    "format": "wav",
    "quality": "high",
    "startTime": 0.0,
    "endTime": 60.0
    }
    ```

    **Response:**
    ```json
    {
    "jobId": "render-job-uuid",
    "status": "processing",
    "estimatedCompletion": "2024-01-15T10:35:00Z"
    }
    ```

    ---

    ## Audio & Sample Management

    ### Upload Sample Audio

    **POST** `/sample/audio`

    Upload audio file to create a new sample.

    **Authentication:** Required

    **Request:** Multipart form data
    ```
    Content-Type: multipart/form-data
    file: [audio file binary]
    name: "My Sample"
    public: true
    scale: "C Major"
    ```

    **Response:**
    ```json
    {
    "sampleId": 12345,
    "audioId": "550e8400-e29b-41d4-a716-446655440000",
    "processing": true
    }
    ```

    **Example Usage:**
    ```javascript
    const formData = new FormData();
    formData.append('file', audioFile);
    formData.append('name', 'My Drum Loop');
    formData.append('public', 'true');

    const response = await fetch('/sample/audio', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}` },
    body: formData
    });
    ```

    ### Get Samples

    **GET** `/sample`

    Browse and search sample library.

    **Authentication:** Optional

    **Query Parameters:**
    - `q` (string) - Search query
    - `owner_id` (integer) - Filter by owner
    - `public` (boolean) - Public samples only
    - `scale` (string) - Musical scale filter
    - `bpm_min` (number) - Minimum BPM
    - `bpm_max` (number) - Maximum BPM
    - `duration_min` (number) - Minimum duration
    - `duration_max` (number) - Maximum duration
    - `limit` (integer) - Results per page
    - `offset` (integer) - Pagination offset

    **Response:**
    ```json
    [
    {
    "sample": {
    "id": 12345,
    "name": "Deep House Bass",
    "created": "2024-01-15T10:30:00Z",
    "audioId": "550e8400-e29b-41d4-a716-446655440000",
    "ownerId": 67890,
    "parentFolderId": 555,
    "public": true,
    "scale": "A Minor",
    "imageId": 123
    },
    "owner": {
    "id": 67890,
    "userName": "bassmaster",
    "verified": true,
    "displayPicture": {
    "url": "https://cdn.twoshot.app/images/profile_67890.jpg"
    }
    },
    "tags": ["house", "bass", "deep", "electronic"],
    "audioInfo": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "duration": 8.0,
    "bpm": 125.0,
    "free": false,
    "licenceId": 789
    },
    "liked": false,
    "likes": 42
    }
    ]
    ```

    ---

    ## AI Generation & Models

    ### Get Available Models

    **GET** `/model`

    Retrieve AI models for audio generation.

    **Authentication:** Optional

    **Query Parameters:**
    - `category` (string) - Filter by model category
    - `sort` (enum) - Sort order: `latest`, `oldest`, `popular`, `random`, `recently_used`
    - `limit` (integer) - Results per page

    **Response:**
    ```json
    [
    {
    "id": 101,
    "name": "MusicGen Large",
    "description": "High-quality music generation model",
    "category": "music_generation",
    "tags": ["music", "ai", "generation"],
    "inputTypes": ["text", "audio"],
    "outputTypes": ["audio"],
    "maxDuration": 30.0,
    "pricing": {
    "free": false,
    "credits": 5
    },
    "parameters": [
    {
    "name": "prompt",
    "type": "text",
    "required": true,
    "description": "Describe the music you want to generate"
    },
    {
    "name": "duration",
    "type": "number",
    "required": false,
    "default": 10.0,
    "min": 5.0,
    "max": 30.0
    }
    ],
    "liked": false,
    "likes": 1337
    }
    ]
    ```

    ### Create Generation Job

    **POST** `/generation`

    Start AI audio generation job.

    **Authentication:** Required

    **Request Body:**
    ```json
    {
    "modelId": 101,
    "inputs": [
    {
    "type": "text",
    "value": "Upbeat electronic dance music with heavy bass"
    },
    {
    "type": "number",
    "value": 15.0
    },
    {
    "type": "audio",
    "audioId": "550e8400-e29b-41d4-a716-446655440000",
    "trimStart": 0.0,
    "trimEnd": 10.0
    }
    ]
    }
    ```

    **Response:**
    ```json
    {
    "jobId": "550e8400-e29b-41d4-a716-446655440099",
    "status": "pending",
    "estimatedCompletion": "2024-01-15T10:35:00Z",
    "creditsUsed": 5
    }
    ```

    **Example Usage:**
    ```javascript
    // Generate music from text prompt
    const generation = await fetch('/generation', {
    method: 'POST',
    headers: {
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json'
    },
    body: JSON.stringify({
    modelId: 101,
    inputs: [
    { type: "text", value: "Chill lo-fi hip hop beat" },
    { type: "number", value: 20.0 }
    ]
    })
    });

    const { jobId } = await generation.json();

    // Poll for completion
    const checkStatus = async () => {
    const status = await fetch(`/generation/${jobId}`)
    .then(r => r.json());

    if (status.status === 'completed') {
    console.log('Generated audio:', status.outputs);
    } else if (status.status === 'failed') {
    console.error('Generation failed:', status.error);
    } else {
    setTimeout(checkStatus, 5000); // Check again in 5 seconds
    }
    };

    checkStatus();
    ```

    ### Get Generation Status

    **GET** `/generation/{jobId}`

    Check status of generation job.

    **Authentication:** Optional

    **Response:**
    ```json
    {
    "id": "550e8400-e29b-41d4-a716-446655440099",
    "status": "completed",
    "progress": 100,
    "created": "2024-01-15T10:30:00Z",
    "completed": "2024-01-15T10:32:15Z",
    "model": {
    "id": 101,
    "name": "MusicGen Large"
    },
    "inputs": [
    {
    "type": "text",
    "value": "Upbeat electronic dance music"
    }
    ],
    "outputs": [
    {
    "audioId": "550e8400-e29b-41d4-a716-446655440100",
    "duration": 15.0,
    "format": "wav"
    }
    ],
    "error": null,
    "creditsUsed": 5
    }
    ```

    ---

    ## User Management

    ### Get Current User

    **GET** `/user`

    **Authentication:** Required

    **Response:**
    ```json
    {
    "id": 12345,
    "userName": "musicmaker2024",
    "firstName": "John",
    "lastName": "Doe",
    "email": "[email protected]",
    "created": "2024-01-01T00:00:00Z",
    "displayPicture": {
    "id": 67890,
    "url": "https://cdn.twoshot.app/images/profile_67890.jpg"
    },
    "verified": true,
    "featured": false,
    "instagram": "johnmusic",
    "soundcloudUrl": "https://soundcloud.com/johnmusic"
    }
    ```

    ### Follow/Unfollow User

    **POST** `/user/{followedUserId}/follow`
    **DELETE** `/user/{followedUserId}/follow`

    **Authentication:** Required

    **Response:**
    ```json
    {
    "following": true,
    "followerCount": 1338
    }
    ```

    ---

    ## Licensing & Subscriptions

    ### Get Available Licenses

    **GET** `/licence`

    **Authentication:** Optional

    **Response:**
    ```json
    [
    {
    "id": 1,
    "name": "Standard License",
    "description": "Use in commercial projects up to 1M streams",
    "price": 9.99,
    "currency": "USD",
    "terms": {
    "commercial": true,
    "streaming_limit": 1000000,
    "attribution_required": false
    }
    }
    ]
    ```

    ### Apply License

    **POST** `/licence/apply`

    Apply license to content for usage rights.

    **Authentication:** Required

    **Request Body:**
    ```json
    {
    "licenceId": 1,
    "contentId": "550e8400-e29b-41d4-a716-446655440000",
    "contentType": "sample"
    }
    ```

    **Response:**
    ```json
    {
    "licenseApplicationId": "license-app-uuid",
    "status": "approved",
    "validUntil": "2025-01-15T10:30:00Z",
    "terms": {
    "commercial": true,
    "streaming_limit": 1000000
    }
    }
    ```

    ---

    ## Error Handling

    ### Error Response Format

    All errors return JSON with consistent structure:

    ```json
    {
    "error": "Invalid request parameters",
    "code": "VALIDATION_ERROR",
    "details": {
    "field": "tempo",
    "message": "Tempo must be between 60 and 200 BPM"
    },
    "timestamp": "2024-01-15T10:30:00Z",
    "path": "/project"
    }
    ```

    ### HTTP Status Codes

    - `200` - Success
    - `201` - Created successfully
    - `400` - Bad Request (validation errors)
    - `401` - Unauthorized (invalid/missing token)
    - `403` - Forbidden (insufficient permissions)
    - `404` - Not Found
    - `429` - Rate Limited
    - `500` - Internal Server Error

    ### Rate Limiting

    API requests are rate-limited per user/IP:

    **Headers:**
    ```
    X-RateLimit-Limit: 1000
    X-RateLimit-Remaining: 995
    X-RateLimit-Reset: 1642252800
    ```

    **Rate Limits:**
    - Anonymous: 100 requests/hour
    - Authenticated: 1000 requests/hour
    - Premium: 5000 requests/hour

    ---

    ## SDK Examples

    ### JavaScript/Node.js

    ```javascript
    class TwoShotAPI {
    constructor(token) {
    this.token = token;
    this.baseURL = 'https://api.twoshot.app';
    }

    async request(endpoint, options = {}) {
    const response = await fetch(`${this.baseURL}${endpoint}`, {
    ...options,
    headers: {
    'Authorization': `Bearer ${this.token}`,
    'Content-Type': 'application/json',
    ...options.headers
    }
    });

    if (!response.ok) {
    throw new Error(`API Error: ${response.status}`);
    }

    return response.json();
    }

    // Create project
    async createProject(projectData) {
    return this.request('/project', {
    method: 'POST',
    body: JSON.stringify(projectData)
    });
    }

    // Generate audio
    async generateAudio(modelId, inputs) {
    return this.request('/generation', {
    method: 'POST',
    body: JSON.stringify({ modelId, inputs })
    });
    }

    // Upload sample
    async uploadSample(file, metadata) {
    const formData = new FormData();
    formData.append('file', file);
    Object.entries(metadata).forEach(([key, value]) => {
    formData.append(key, value);
    });

    return this.request('/sample/audio', {
    method: 'POST',
    body: formData,
    headers: {} // Let browser set Content-Type for FormData
    });
    }
    }

    // Usage
    const api = new TwoShotAPI('your-token-here');

    // Create a project
    const project = await api.createProject({
    name: "My Track",
    tempo: 128,
    tracks: []
    });

    // Generate AI audio
    const generation = await api.generateAudio(101, [
    { type: "text", value: "Upbeat house music" }
    ]);
    ```

    ### Python

    ```python
    import requests
    import json

    class TwoShotAPI:
    def __init__(self, token):
    self.token = token
    self.base_url = 'https://api.twoshot.app'
    self.headers = {
    'Authorization': f'Bearer {token}',
    'Content-Type': 'application/json'
    }

    def request(self, endpoint, method='GET', data=None, files=None):
    url = f'{self.base_url}{endpoint}'
    headers = self.headers.copy()

    if files:
    headers.pop('Content-Type') # Let requests set it for multipart

    response = requests.request(
    method=method,
    url=url,
    headers=headers,
    json=data if not files else None,
    files=files
    )

    response.raise_for_status()
    return response.json()

    def create_project(self, project_data):
    return self.request('/project', 'POST', project_data)

    def generate_audio(self, model_id, inputs):
    return self.request('/generation', 'POST', {
    'modelId': model_id,
    'inputs': inputs
    })

    def upload_sample(self, file_path, name, public=True):
    with open(file_path, 'rb') as f:
    files = {'file': f}
    data = {'name': name, 'public': str(public).lower()}
    return self.request('/sample/audio', 'POST', files=files)

    # Usage
    api = TwoShotAPI('your-token-here')

    # Generate audio
    generation = api.generate_audio(101, [
    {"type": "text", "value": "Chill lo-fi beat"}
    ])

    print(f"Generation started: {generation['jobId']}")
    ```

    ---

    ## Complete Endpoint Reference

    <details>
    <summary>Click to expand all 61 public endpoints</summary>

    ### Project Management (8 endpoints)
    - `POST /project` - Create project
    - `GET /project` - Get projects with filters
    - `GET /project/{id}` - Get specific project
    - `PUT /project/{id}` - Update project
    - `DELETE /project/{id}` - Delete project
    - `POST /project/{id}/clone` - Clone project
    - `POST /project/{id}/render` - Render project
    - `POST /project/render` - Render project data

    ### Audio & Sample Management (17 endpoints)
    - `GET /audio` - Get audio files
    - `GET /audio/{audioId}` - Get audio metadata
    - `GET /audio/{audioId}/play` - Get playback URL
    - `GET /audio/{audioId}/bpm` - Get audio BPM
    - `GET /audio/{audioId}/download` - Download audio
    - `GET /audio/{audioId}/stem` - Get stem data
    - `GET /audio/download` - Bulk download
    - `GET /sample` - Browse samples
    - `POST /sample/audio` - Upload sample
    - `GET /sample/{sampleId}` - Get sample details
    - `GET /sample/{sampleId}/preview/{audioId}` - Preview sample
    - `POST/DELETE /sample/{sampleId}/like` - Like/unlike sample
    - `GET /sample/{sampleId}/download/{audioId}` - Download sample
    - `PATCH /sample/{sampleId}/visibility` - Update visibility
    - `PATCH /sample/{sampleId}/rename` - Rename sample
    - `PATCH /sample/{sampleId}/art` - Update artwork

    ### AI Generation & Models (9 endpoints)
    - `GET /model` - Get available models
    - `GET /model/preset` - Get model presets
    - `GET /model/tag` - Get model tags
    - `GET /model/{modelId}` - Get model details
    - `POST/DELETE /model/{modelId}/like` - Like/unlike model
    - `POST/DELETE /model/{modelId}/tag/{tag}` - Tag/untag model
    - `POST /generation` - Create generation job
    - `GET /generation` - Get user generations
    - `GET /generation/{jobId}` - Get generation status

    ### User Management (7 endpoints)
    - `GET /user` - Get current user
    - `GET /user/info` - Get user info
    - `GET /user/referred` - Get referred users
    - `PATCH /user/picture` - Update profile picture
    - `PATCH /user/instagram` - Update Instagram
    - `GET /user/{profileId}` - Get user profile
    - `POST/DELETE /user/{userId}/follow` - Follow/unfollow

    ### And 20 more endpoint categories...

    </details>

    ---

    ## Support & Resources

    - **API Status:** [status.twoshot.app](https://status.twoshot.app)
    - **Community Discord:** [twoshot.app/discord](https://twoshot.app/discord)
    - **GitHub Issues:** Report bugs and feature requests
    - **Rate Limits:** Check current limits in response headers

    **Need Help?**
    - Technical questions: [email protected]
    - Business inquiries: [email protected]
    - Bug reports: Create an issue on GitHub