# Custom Ingestion API (Coming Soon)

The Ingestion API allows you to send custom customer or activity data into MadKudu in real time. It’s ideal for ingesting product usage events, marketing actions, or any proprietary/purchased signals—especially when native integrations (e.g. Salesforce, HubSpot) are not an option.

Whether it’s product usage from your app, marketing events from custom systems, or any proprietary or purchased signals, this API gives you a single, secure, schema-flexible way to get your data into the MadKudu pipeline.

<figure><img src="https://1835787252-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FV8CtRF59w8oxkhPMGwwB%2Fuploads%2FFwXg5pgQGnMTldZ3LnkE%2FPasted_Image_7_14_25__8_24%E2%80%AFAM.png?alt=media&#x26;token=89cd7e5c-4f49-49ba-aab5-8378f1c9ecd1" alt=""><figcaption></figcaption></figure>

## When to use the Ingestion API

Use this API when you need to send data to MadKudu

* that can’t be synced through existing native integrations (see integration list)
* where going through Snowflake, BigQuery or S3 is a heavier lift

If your data is already piped into Segment or your warehouse, we recommend starting with those integrations.

## Supported data objects

<table><thead><tr><th width="216.16668701171875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>contact event</code></td><td>action or behavior (e.g. “signed_up”) tied to a person</td></tr><tr><td><code>account event</code></td><td>action or behavior (e.g. “new funding round”) tied to a company</td></tr><tr><td><code>contact</code></td><td>Update to person-level attributes</td></tr><tr><td><code>account</code></td><td>Update to company/account-level attributes</td></tr></tbody></table>

## How the Ingestion API works

There 2 ingestion modes to choose from&#x20;

1. Lightweight JSON file upload directly to the API  (payloads up to 1 MB)
2. Heavyweight JSONL file uploads to a S3 URL (gzip file up to 2 GB)

## Format&#x20;

Each ingestion mode expects the same record format.\
Files **must** follow the schema defined in the Upload JSON Schema Reference or they will be **rejected**

## Ingestion mode 1: Lightweight JSON upload to API&#x20;

Use this mode for small, frequent uploads (e.g. real-time or near real-time updates).

* **Payload limit**: Up to 1 MB (uncompressed)
* **Content type**: `application/json`
* **Upload method**: `POST` `https://madapi.madkudu.com/ingestion/upload-json`
* **Recommended for**:
  * Testing integrations
  * Event-based ingestion
  * Low-latency use cases

## Import data via API

> Directly upload contact, account, or activity data in JSON format through the API for immediate processing.

```json
{"openapi":"3.1.0","info":{"title":"MadAPI","version":"0.0.0"},"tags":[{"name":"Ingestion"}],"servers":[{"url":"https://madapi.madkudu.com","description":"Production server","variables":{}},{"url":"https://madapi.wisekudu.com","description":"Staging server","variables":{}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"JsonUploadResponse":{"type":"object","required":["status","message","file_key"],"properties":{"status":{"type":"string","description":"Upload status"},"message":{"type":"string","description":"Upload message"},"file_key":{"type":"string","description":"Generated file key for the uploaded data"}},"description":"Response model for JSON upload"},"JsonUploadRequest":{"type":"object","required":["stream","data"],"properties":{"stream":{"allOf":[{"$ref":"#/components/schemas/AcceptedStream"}],"description":"Stream type for the data"},"data":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},{"type":"array","items":{"$ref":"#/components/schemas/Account"}},{"type":"array","items":{"$ref":"#/components/schemas/ContactEvent"}},{"type":"array","items":{"$ref":"#/components/schemas/AccountEvent"}}],"description":"Data array - contents depend on stream type"}},"description":"Request model for direct JSON upload"},"AcceptedStream":{"type":"string","enum":["account","account_event","contact","contact_event"],"description":"Accepted streams for customer data"},"Contact":{"type":"object","required":["contact_id","email","contact_properties"],"properties":{"type":{"type":"string","enum":["contact"],"description":"Type identifier for contact","default":"contact"},"contact_id":{"type":"string","description":"Unique identifier for the contact"},"email":{"type":"string","description":"Email address of the contact"},"contact_properties":{"type":"object","unevaluatedProperties":{},"description":"Additional contact properties"}},"description":"Contact model for ingestion"},"Account":{"type":"object","required":["account_id","domain","account_properties"],"properties":{"type":{"type":"string","enum":["account"],"description":"Type identifier for account","default":"account"},"account_id":{"type":"string","description":"Unique identifier for the account"},"domain":{"type":"string","description":"Domain of the account"},"account_properties":{"type":"object","unevaluatedProperties":{},"description":"Additional account properties"}},"description":"Account model for ingestion"},"ContactEvent":{"type":"object","required":["event_id","contact_id","event","event_timestamp","event_properties"],"properties":{"event_type":{"type":"string","enum":["contact_event"],"description":"Event type identifier","default":"contact_event"},"event_id":{"type":"string","description":"Unique identifier for the event"},"contact_id":{"type":"string","description":"Contact identifier for the event"},"event":{"type":"string","description":"Event name"},"event_timestamp":{"type":"string","format":"date-time","description":"Timestamp when the event occurred (ISO 8601 format)"},"event_properties":{"type":"object","unevaluatedProperties":{},"description":"Additional event properties"}},"description":"Contact event model for ingestion"},"AccountEvent":{"type":"object","required":["event_id","account_id","event","event_timestamp","event_properties"],"properties":{"event_type":{"type":"string","enum":["account_event"],"description":"Event type identifier","default":"account_event"},"event_id":{"type":"string","description":"Unique identifier for the event"},"account_id":{"type":"string","description":"Account identifier for the event"},"event":{"type":"string","description":"Event name"},"event_timestamp":{"type":"string","format":"date-time","description":"Timestamp when the event occurred (ISO 8601 format)"},"event_properties":{"type":"object","unevaluatedProperties":{},"description":"Additional event properties"}},"description":"Account event model for ingestion"}}},"paths":{"/ingestion/upload-json":{"post":{"operationId":"Ingestion_uploadJson","summary":"Import data via API","description":"Directly upload contact, account, or activity data in JSON format through the API for immediate processing.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonUploadResponse"}}}}},"tags":["Ingestion"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonUploadRequest"}}}}}}}}
```

## Ingestion mode 2: Heavyweight JSONL file upload to S3

Use this mode for large batch uploads (e.g. daily exports or backfills).

Your input file must be in **JSONL** format, then compressed using **gzip**.&#x20;

* **Payload limit**: Up to 2 GB (compressed as `.jsonl.gz`)
* **Upload method**:
  1. Request a pre-signed S3 URL from the API `POST /ingestion/request-upload-url`
  2. Upload the `.jsonl.gz` file directly to the S3 URL with the command line, [AWS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html), Postman or other script&#x20;

     Example of request

```
curl -X PUT \
  -T your-file.jsonl.gz \
  "https://madkudu-ingestion.s3.amazonaws.com/tmp/your-upload-id.json.gz?...[signature]"
```

3. Get confirmation of the upload from the API  `POST /ingestion/notify-upload-complete`

* **Recommended for**:
  * Large CRM exports
  * Historical event backfills
  * Low-frequency, high-volume ingestion

## Generate Upload Url

> Generate a presigned URL for file upload.

```json
{"openapi":"3.1.0","info":{"title":"MadAPI","version":"0.0.0"},"tags":[{"name":"Ingestion"}],"servers":[{"url":"https://madapi.madkudu.com","description":"Production server","variables":{}},{"url":"https://madapi.wisekudu.com","description":"Staging server","variables":{}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"UploadResponse":{"type":"object","required":["upload_url","file_key"],"properties":{"upload_url":{"type":"string","description":"Presigned URL for file upload"},"file_key":{"type":"string","description":"Unique file key for the uploaded file"}}},"UploadRequest":{"type":"object","required":["stream","content_type","content_encoding"],"properties":{"stream":{"allOf":[{"$ref":"#/components/schemas/AcceptedStream"}],"description":"Stream type for the data"},"content_type":{"allOf":[{"$ref":"#/components/schemas/ContentType"}],"description":"Content type of the file to upload"},"content_encoding":{"allOf":[{"$ref":"#/components/schemas/ContentEncoding"}],"description":"Content encoding for the file"}}},"AcceptedStream":{"type":"string","enum":["account","account_event","contact","contact_event"],"description":"Accepted streams for customer data"},"ContentType":{"type":"string","enum":["application/jsonl"],"description":"Content types for customer data"},"ContentEncoding":{"type":"string","enum":["gzip"],"description":"Content encodings for customer data"}}},"paths":{"/ingestion/generate-upload-url":{"post":{"operationId":"Ingestion_generateUploadUrl","summary":"Generate Upload Url","description":"Generate a presigned URL for file upload.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResponse"}}}}},"tags":["Ingestion"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadRequest"}}}}}}}}
```

### GZIP your file and Upload it to the Upload URL

Here is a Python script  &#x20;

```python
import requests
import gzip
import shutil


def upload_jsonl_to_s3_presigned_url(file_path, presigned_url):
    with open(file_path, "rb") as f:
        response = requests.put(
            presigned_url,
            data=f,
            headers={"Content-Type": "application/jsonl", "Content-Encoding": "gzip"},
        )

    if response.status_code == 200:
        print("Upload successful")
    else:
        print(f"Upload failed: {response.status_code} - {response.text}")

file_path = "valid.jsonl"

# Compress the file
compressed_file = file_path + ".gz"
with open(file_path, "rb") as f_in:
    with gzip.open(compressed_file, "wb") as f_out:
        shutil.copyfileobj(f_in, f_out)

# Get presigned upload url from previous step 
upload_jsonl_to_s3_presigned_url(compressed_file, presigned_url)
```

## Confirm Upload

> Confirm that a file has been uploaded.

```json
{"openapi":"3.1.0","info":{"title":"MadAPI","version":"0.0.0"},"tags":[{"name":"Ingestion"}],"servers":[{"url":"https://madapi.madkudu.com","description":"Production server","variables":{}},{"url":"https://madapi.wisekudu.com","description":"Staging server","variables":{}}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"ConfirmUploadResponse":{"type":"object","required":["status","message"],"properties":{"status":{"type":"string","description":"Upload confirmation status"},"message":{"type":"string","description":"Confirmation message"}}},"ConfirmUploadRequest":{"type":"object","required":["file_key"],"properties":{"file_key":{"type":"string","description":"File key from the upload response"}}}}},"paths":{"/ingestion/confirm-upload":{"post":{"operationId":"Ingestion_confirmUpload","summary":"Confirm Upload","description":"Confirm that a file has been uploaded.","parameters":[],"responses":{"200":{"description":"The request has succeeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmUploadResponse"}}}}},"tags":["Ingestion"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmUploadRequest"}}}}}}}}
```
