How to Handle File Attachments in Genesys Cloud Web Messaging with Python
What You Will Build
- A Python script that authenticates with Genesys Cloud, uploads a binary file to the media service, and sends a Web Messaging conversation message containing the file attachment reference.
- This tutorial uses the Genesys Cloud Platform API v2 and the
genesys-cloud-pySDK. - The implementation covers MIME type validation, size limit enforcement, and the two-step process required to attach files to messages.
Prerequisites
- OAuth Client Type: Public or Confidential Client with
agent:conversation:readandanalytics:conversation:details:readscopes are not sufficient for sending. You needconversation:sendandmedia:uploadscopes. - SDK Version:
genesys-cloud-pyversion 5.0.0 or higher. - Language/Runtime: Python 3.8+.
- External Dependencies:
genesys-cloud-pyrequestspython-magic(for robust MIME type detection, though standardmimetypesis used in examples for simplicity).
Authentication Setup
Genesys Cloud uses OAuth 2.0 for API access. For file uploads in Web Messaging, you typically act on behalf of an agent or a bot. The most common flow for server-side integrations is the Client Credentials flow.
import os
from purecloud_platform_client import PureCloudPlatformClientV2, Configuration
def get_platform_client():
"""
Initializes the Genesys Cloud Platform Client using Client Credentials.
"""
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
environment = os.environ.get("GENESYS_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET environment variables are required.")
config = Configuration(
host=f"https://{environment}",
client_id=client_id,
client_secret=client_secret
)
platform_client = PureCloudPlatformClientV2(config)
return platform_client
OAuth Scopes Required:
conversation:send: To post messages to the Web Messaging conversation.media:upload: To upload binary data to the Genesys Cloud media service.
Implementation
Step 1: Validate File Constraints Before Upload
Genesys Cloud Web Messaging imposes strict limits on file attachments. Attempting to upload a file that violates these constraints will result in an HTTP 400 Bad Request or 413 Payload Too Large.
Constraints:
- Maximum Size: 10 MB (10,485,760 bytes).
- Allowed MIME Types:
- Images:
image/png,image/jpeg,image/gif,image/webp - Documents:
application/pdf,text/plain,text/csv - Archives:
application/zip,application/x-zip-compressed - Office Docs:
application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document(docx), etc.
- Images:
Always validate on the client side before initiating the network request to save bandwidth and avoid API errors.
import os
import mimetypes
from typing import Tuple
ALLOWED_MIME_TYPES = {
'image/png',
'image/jpeg',
'image/gif',
'image/webp',
'application/pdf',
'text/plain',
'text/csv',
'application/zip',
'application/x-zip-compressed',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
}
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024 # 10 MB
def validate_file(file_path: str) -> Tuple[bool, str]:
"""
Validates the file size and MIME type against Genesys Cloud constraints.
Returns:
Tuple[bool, str]: (is_valid, error_message)
"""
if not os.path.exists(file_path):
return False, "File does not exist."
file_size = os.path.getsize(file_path)
if file_size > MAX_FILE_SIZE_BYTES:
return False, f"File size {file_size} bytes exceeds the 10 MB limit."
# Guess MIME type based on extension
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type is None:
return False, "Could not determine MIME type. Ensure the file has a valid extension."
if mime_type not in ALLOWED_MIME_TYPES:
return False, f"MIME type '{mime_type}' is not allowed. Allowed types: {ALLOWED_MIME_TYPES}"
return True, "Valid"
Step 2: Upload the File to Genesys Cloud Media Service
Genesys Cloud does not accept binary data directly in the Web Messaging message body. You must first upload the file to the Genesys Cloud Media Service using the POST /api/v2/media/upload endpoint. This returns a mediaUri which is then referenced in the message.
This step requires the media:upload scope.
import requests
from purecloud_platform_client import PureCloudPlatformClientV2
def upload_file_to_media_service(platform_client: PureCloudPlatformClientV2, file_path: str) -> str:
"""
Uploads a file to the Genesys Cloud Media Service.
Args:
platform_client: Authenticated PureCloudPlatformClientV2 instance.
file_path: Path to the local file.
Returns:
str: The mediaUri of the uploaded file.
"""
# Get the access token from the platform client
# Note: In production, handle token refresh logic if the token expires mid-operation
token = platform_client.auth_client.get_access_token()
if not token:
raise Exception("Failed to retrieve access token.")
url = f"{platform_client.configuration.host}/api/v2/media/upload"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
# Content-Type is set automatically by requests when using files=
}
# Open file in binary mode
with open(file_path, 'rb') as f:
files = {
'file': (os.path.basename(file_path), f, 'application/octet-stream')
}
try:
response = requests.post(url, headers=headers, files=files)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
if response.status_code == 413:
raise Exception("File too large. Genesys Cloud limit is 10 MB.")
elif response.status_code == 400:
raise Exception(f"Bad Request: {response.text}")
else:
raise Exception(f"Upload failed with status {response.status_code}: {response.text}")
data = response.json()
# The response contains the mediaUri
if 'mediaUri' not in data:
raise Exception("Unexpected response format from media upload service.")
return data['mediaUri']
Expected Response:
{
"mediaUri": "https://mypurecloud.com/api/v2/media/attachments/abc-123-def-456",
"fileName": "invoice.pdf",
"fileSize": 102400,
"mimeType": "application/pdf"
}
Step 3: Send the Web Messaging Message with Attachment
Once you have the mediaUri, you can send a message to the Web Messaging conversation. You must use the POST /api/v2/conversations/messages endpoint. The message body must include the attachments array with the mediaUri.
This step requires the conversation:send scope.
from purecloud_platform_client import MessageBody, Attachment
def send_web_messaging_message(platform_client: PureCloudPlatformClientV2, conversation_id: str, media_uri: str, file_name: str) -> dict:
"""
Sends a Web Messaging message with a file attachment.
Args:
platform_client: Authenticated PureCloudPlatformClientV2 instance.
conversation_id: The ID of the Web Messaging conversation.
media_uri: The mediaUri returned from the upload step.
file_name: The original name of the file for display purposes.
Returns:
dict: The response from the Genesys Cloud API.
"""
from purecloud_platform_client import ApiClient
# Initialize the ConversationApi client
conversation_api = platform_client.ConversationApi()
# Create the attachment object
attachment = Attachment()
attachment.media_uri = media_uri
attachment.file_name = file_name
# Create the message body
message_body = MessageBody()
message_body.attachments = [attachment]
# Optional: Add text alongside the attachment
message_body.text = "Please find the attached document."
try:
# Send the message
response = conversation_api.post_conversations_messages(
conversation_id=conversation_id,
body=message_body,
conversation_type="webmessaging" # Explicitly specify type if needed, though usually inferred from ID
)
return response
except Exception as e:
print(f"Failed to send message: {e}")
raise
Expected Request Body:
{
"text": "Please find the attached document.",
"attachments": [
{
"mediaUri": "https://mypurecloud.com/api/v2/media/attachments/abc-123-def-456",
"fileName": "invoice.pdf"
}
]
}
Complete Working Example
This script combines validation, upload, and message sending into a single workflow. It assumes you have a valid conversation_id for an active Web Messaging session.
import os
import sys
import mimetypes
from typing import Tuple
# Import Genesys Cloud SDK
from purecloud_platform_client import PureCloudPlatformClientV2, Configuration, MessageBody, Attachment
import requests
# --- Configuration ---
ALLOWED_MIME_TYPES = {
'image/png', 'image/jpeg', 'image/gif', 'image/webp',
'application/pdf', 'text/plain', 'text/csv',
'application/zip', 'application/x-zip-compressed',
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}
MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024
def get_platform_client():
client_id = os.environ.get("GENESYS_CLIENT_ID")
client_secret = os.environ.get("GENESYS_CLIENT_SECRET")
environment = os.environ.get("GENESYS_ENVIRONMENT", "mypurecloud.com")
if not client_id or not client_secret:
raise ValueError("Set GENESYS_CLIENT_ID and GENESYS_CLIENT_SECRET env vars.")
config = Configuration(
host=f"https://{environment}",
client_id=client_id,
client_secret=client_secret
)
return PureCloudPlatformClientV2(config)
def validate_file(file_path: str) -> Tuple[bool, str]:
if not os.path.exists(file_path):
return False, "File not found."
if os.path.getsize(file_path) > MAX_FILE_SIZE_BYTES:
return False, "File exceeds 10 MB limit."
mime_type, _ = mimetypes.guess_type(file_path)
if not mime_type or mime_type not in ALLOWED_MIME_TYPES:
return False, f"MIME type '{mime_type}' not allowed."
return True, "OK"
def upload_file(platform_client: PureCloudPlatformClientV2, file_path: str) -> str:
token = platform_client.auth_client.get_access_token()
if not token:
raise Exception("No access token available.")
url = f"{platform_client.configuration.host}/api/v2/media/upload"
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
with open(file_path, 'rb') as f:
files = {'file': (os.path.basename(file_path), f, 'application/octet-stream')}
response = requests.post(url, headers=headers, files=files)
response.raise_for_status()
data = response.json()
if 'mediaUri' not in data:
raise Exception("Invalid upload response.")
return data['mediaUri']
def send_message(platform_client: PureCloudPlatformClientV2, conversation_id: str, media_uri: str, file_name: str) -> dict:
conversation_api = platform_client.ConversationApi()
attachment = Attachment()
attachment.media_uri = media_uri
attachment.file_name = file_name
body = MessageBody()
body.attachments = [attachment]
body.text = "Here is your file."
try:
response = conversation_api.post_conversations_messages(
conversation_id=conversation_id,
body=body,
conversation_type="webmessaging"
)
return response
except Exception as e:
print(f"Message send error: {e}")
raise
def main():
if len(sys.argv) != 3:
print("Usage: python send_attachment.py <conversation_id> <file_path>")
sys.exit(1)
conversation_id = sys.argv[1]
file_path = sys.argv[2]
# 1. Validate
is_valid, msg = validate_file(file_path)
if not is_valid:
print(f"Validation failed: {msg}")
sys.exit(1)
# 2. Authenticate
platform_client = get_platform_client()
# 3. Upload
try:
media_uri = upload_file(platform_client, file_path)
print(f"File uploaded successfully. Media URI: {media_uri}")
except Exception as e:
print(f"Upload failed: {e}")
sys.exit(1)
# 4. Send Message
try:
response = send_message(platform_client, conversation_id, media_uri, os.path.basename(file_path))
print(f"Message sent successfully. Message ID: {response.id if hasattr(response, 'id') else 'N/A'}")
except Exception as e:
print(f"Send failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Common Errors & Debugging
Error: 400 Bad Request on Media Upload
- Cause: The file MIME type is not supported, or the file is corrupted.
- Fix: Verify the file extension matches the content. Genesys Cloud checks the header and sometimes the magic bytes. Ensure you are using one of the
ALLOWED_MIME_TYPESlisted in Step 1. - Code Fix: Add explicit
Content-Typein thefilestuple ifmimetypes.guess_typeis failing.
# Explicitly set MIME type in the files tuple
files = {
'file': (os.path.basename(file_path), f, 'application/pdf') # Hardcoded for testing
}
Error: 413 Payload Too Large
- Cause: The file exceeds the 10 MB limit.
- Fix: Compress the file or split it into smaller parts. Genesys Cloud Web Messaging does not support chunked uploads for files larger than 10 MB in this context.
Error: 401 Unauthorized on Message Send
- Cause: The OAuth token does not have the
conversation:sendscope. - Fix: Check your OAuth client configuration in the Genesys Cloud Admin Console. Go to Setup > Applications > OAuth > [Your Client] and ensure
conversation:sendis checked.
Error: 403 Forbidden
- Cause: The user associated with the OAuth token does not have permission to send messages in the target conversation, or the conversation is closed.
- Fix: Ensure the conversation is active. For Web Messaging, the conversation must be in a state that accepts messages (e.g.,
ACTIVEorRESOLVEDif history is allowed, but typicallyACTIVE).