HubSpot Import API - Getting 415 Error When Uploading Contact Data

I’m trying to upload contact records to HubSpot using their import API but keep getting a 415 status code error. My contacts have basic info like name, age, phone, location, and website URL. I tested with both CSV and Excel formats but neither works.

import requests
import json

api_endpoint = 'http://api.hubapi.com/crm/v3/imports?hapikey=abc....xyz'

data_file = "contact_upload.csv"

request_headers = {'accept': 'application/json'}

payload = {
    "name": "bulk_contact_import",
    "files": [
        {
            "fileName": data_file,
            "fileFormat": "CSV",
            "fileImportPage": {
                "hasHeader": True,
                "columnMappings": [
                    {
                        "ignored": False,
                        "columnName": "Name",
                        "idColumnType": None,
                        "propertyName": "firstname",
                        "foreignKeyType": None,
                        "columnObjectType": "CONTACT",
                        "associationIdentifiedColumn": False
                    },
                    {
                        "ignored": False,
                        "columnName": "Website",
                        "idColumnType": None,
                        "propertyName": "website",
                        "foreignKeyType": None,
                        "columnObjectType": "CONTACT",
                        "associationIdentifiedColumn": False
                    },
                    {
                        "ignored": False,
                        "columnName": "Age",
                        "idColumnType": None,
                        "propertyName": "age_field",
                        "foreignKeyType": None,
                        "columnObjectType": "CONTACT",
                        "associationIdentifiedColumn": False
                    }
                ]
            }
        }
    ]
}

response = requests.post(url=api_endpoint, data=payload, headers=request_headers)

print(response.status_code)
print(response.text)

The file path is correct and my API key works for other calls. I double checked that my column names match what’s in HubSpot. No test data shows up in my account so the import is definitely failing. Has anyone dealt with this 415 error before? What am I missing in my request format?

you’re missing the file data in your request. use the files parameter instead of data, and switch to multipart/form-data encoding. try files={'files': open(data_file, 'rb')} and put your JSON config in a separate parameter. the 415 error usually means wrong content-type for file uploads.

your API endpoint’s using http instead of https - that’s the problem. HubSpot’s API only accepts secure connections and throws 415 errors otherwise. also, your payload structure’s wrong. the files array should just reference uploaded files, not contain the actual file config.

Had this exact issue during a CRM migration with bulk lead imports. The problem isn’t just multipart encoding - you’re sending file config without the actual file content. HubSpot’s import API needs both JSON config AND file data in one multipart request. Ditch the data parameter completely and do this: with open(data_file, ‘rb’) as f: files = { ‘files’: (data_file, f, ‘text/csv’), ‘importRequest’: (None, json.dumps(payload), ‘application/json’) } response = requests.post(url=api_endpoint, files=files, headers={‘accept’: ‘application/json’}) The trick is treating JSON config like a file parameter too. I wasted two days on this before figuring out HubSpot wants everything as multipart file uploads, even metadata. Also, check your property names exist in HubSpot first - the API fails silently on bad custom properties.

The Problem: You’re attempting to import data into HubSpot CRM using their API, but receive a 415 Unsupported Media Type error. Your current code uses application/json, which is incorrect for file uploads.

:thinking: Understanding the “Why” (The Root Cause):

The HubSpot CRM import API expects data to be sent using multipart/form-data encoding for file uploads. Your code incorrectly attempts to send the CSV data within a JSON payload. The 415 Unsupported Media Type error indicates the server doesn’t recognize the application/json content type when it expects a file upload. Even if the content type were correct, embedding the CSV data directly into the JSON body isn’t how the HubSpot API handles file imports. The API requires both the import configuration (in JSON format) and the actual CSV file to be sent as separate parts of a single multipart request.

:gear: Step-by-Step Guide:

Step 1: Correct the Request and File Handling: The following code demonstrates the correct way to upload your CSV data to HubSpot using the requests library. It uses the files parameter to send both the CSV file and the JSON import request configuration in a single multipart/form-data request. Crucially, it reads the CSV file’s contents into memory before sending it, ensuring the API receives the data correctly. We also use a more robust error handling mechanism to provide clear feedback based on the server response.

from io import StringIO
import requests
import json
import pandas as pd

#Load your CSV into a pandas DataFrame
my_dataframe = pd.read_csv("contact_upload.csv")

#Convert the DataFrame to CSV in memory
csv_buffer = StringIO()
my_dataframe.to_csv(csv_buffer, index=False)
csv_content = csv_buffer.getvalue()

hubspot_token = 'your_hubspot_token' #Replace with your actual token
import_url = 'https://api.hubapi.com/crm/v3/imports' #Note: HTTPS

files = {
    'files': ('contacts.csv', csv_content, 'text/csv'),
    'importRequest': (None, json.dumps({
        "name": "bulk_contact_import",
        "columnMappings": [
            {"ignored": False, "columnName": "Name", "propertyName": "firstname"},
            {"ignored": False, "columnName": "Website", "propertyName": "website"},
            {"ignored": False, "columnName": "Age", "propertyName": "age_field"}
            # Add more mappings as needed...
        ]
    }), 'application/json')
}

headers = {
    'Authorization': f'Bearer {hubspot_token}'
}

response = requests.post(import_url, files=files, headers=headers)

if response.status_code == 200:
    print('Upload completed successfully!')
    print(response.json()) # Inspect the response for success details.
elif response.status_code == 400: # Example specific error handling
    print(f"Bad Request: {response.json()}")
elif response.status_code == 401: # Example specific error handling
    print("Unauthorized: Check your HubSpot token")
else:
    print(f'Upload failed: {response.status_code} - {response.text}')
    print(response.headers) #Inspect headers for any clues.

Step 2: Verify DataFrame Columns: Ensure that the column names in your pandas DataFrame (my_dataframe) precisely match the property names in your HubSpot CRM. Even minor discrepancies (case sensitivity, extra spaces) will cause import failures. Test with a small subset of your data first to pinpoint mapping issues.

Step 3: Authenticate Correctly: Double-check that your_hubspot_token is a valid and correctly formatted HubSpot API key with the necessary permissions for contact imports.

Step 4: Handle Potential Errors: Implement more sophisticated error handling to address various HTTP status codes and API responses. The example shows basic handling of 400 and 401 errors, but you should add more checks for various HubSpot API error codes.

:mag: Common Pitfalls & What to Check Next:

  • Authentication: Incorrect or missing hubspot_token.
  • Rate Limits: HubSpot API has rate limits. For large datasets, consider pagination or batch processing.
  • Data Validation: Thoroughly validate your data before importing. Check for data types, missing values, and inconsistencies.
  • HubSpot Property Names: Confirm that all property names in your CSV match the exact names of your HubSpot properties. Use the HubSpot API to retrieve a list of your contact properties if needed.
  • Character Encoding: Ensure your CSV file is encoded as UTF-8 without a Byte Order Mark (BOM).

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!

Your main problem is sending the payload as regular form data instead of multipart form data with the actual file. That 415 error means HubSpot expects multipart encoding for uploads.

You need to completely restructure this. Put your JSON config in the importRequest parameter and attach the CSV file separately. Don’t set the Content-Type header - let requests handle the multipart encoding.

I hit this same issue last year migrating our customer database. Use the files parameter for both the config JSON and the actual file. Here’s the key part: the configuration needs to be passed as a file-like object too, not regular form data.

Also, make sure your CSV is UTF-8 without BOM. HubSpot gets picky about character encoding even when everything else looks right.

You’re getting a 415 error because HubSpot wants the actual file bytes, but you’re only sending metadata. Your code references a CSV file but never actually reads or uploads the content.

HubSpot’s import API needs multipart form data with the file attached. You’re missing the file handling completely.

Honestly though, HubSpot’s import API is a nightmare. Between multipart encoding, file handling, JSON formatting, and their terrible error messages, you’ll waste more time debugging than importing.

I hit this same wall when we needed to sync contact data from multiple sources. Instead of fighting their API, I built it in Latenode. It handles file uploads, authentication, and data mapping without the technical headaches.

Set up a workflow that reads CSV files, validates data, maps columns to HubSpot fields, and handles bulk imports automatically. Now our marketing team uploads contacts directly without touching code.

The visual interface makes column mapping dead simple. Plus you get error handling and retry logic built in.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.