Upload Contacts CSV
Get a presigned S3 URL to upload a contacts CSV for a campaign
Get a presigned S3 URL to upload a contacts CSV for a campaign
Uploading contacts is a two-step process. First call this endpoint to get a presigned upload URL, then PUT your CSV directly to that URL.
theme={null}
# Step 1: Get upload URL
curl -X POST https://app.paladin.northmanngrp.com/api/v1/s3/presigned-upload-url \
-H "X-API-Key: YOUR_API_KEY"
# Response:
# { "upload_url": "https://...", "s3_key": "campaigns/..." }
# Step 2: Upload the CSV
curl -X PUT "https://presigned-url..." \
-H "Content-Type: text/csv" \
--data-binary @contacts.csvUse the s3_key from the response as the source_url when creating a campaign.
CSV format#
The CSV must include a phone_number column. Any additional columns are passed as initial_context to each call, making them available as template variables in the workflow.
theme={null}
phone_number,customer_name,plan
+14155550100,Jane Smith,premium
+14155550101,Bob Jones,basicOpenAPI#
POST /api/v1/s3/presigned-upload-url
openapi: 3.1.0
info:
title: Paladin API
description: API for the Paladin app
version: 1.0.0
servers:
- url: https://app.paladin.northmanngrp.com
description: Production
security: []
paths:
/api/v1/s3/presigned-upload-url:
post:
tags:
- main
- s3
summary: Generate a presigned URL for direct CSV upload
description: >-
Generate a presigned PUT URL for direct CSV file upload to S3/MinIO.
This endpoint enables browser-to-storage uploads without passing through
the backend
Access Control:
* All authenticated users can upload CSV files scoped to their
organization.
* Files are stored with organization-scoped keys for multi-tenancy.
Returns:
* upload_url: Presigned URL (valid for 15 minutes) for PUT request
* file_key: Unique storage key to use as source_id in campaign creation
* expires_in: URL expiration time in seconds
operationId: get_presigned_upload_url_api_v1_s3_presigned_upload_url_post
parameters:
- name: authorization
in: header
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Authorization
- name: X-API-Key
in: header
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: X-Api-Key
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PresignedUploadUrlRequest'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/PresignedUploadUrlResponse'
'404':
description: Not found
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
components:
schemas:
PresignedUploadUrlRequest:
properties:
file_name:
type: string
pattern: .*\.csv$
title: File Name
description: CSV filename
file_size:
type: integer
maximum: 10485760
exclusiveMinimum: 0
title: File Size
description: File size in bytes (max 10MB)
content_type:
type: string
title: Content Type
description: File content type
default: text/csv
type: object
required:
- file_name
- file_size
title: PresignedUploadUrlRequest
PresignedUploadUrlResponse:
properties:
upload_url:
type: string
title: Upload Url
file_key:
type: string
title: File Key
expires_in:
type: integer
title: Expires In
type: object
required:
- upload_url
- file_key
- expires_in
title: PresignedUploadUrlResponse
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
input:
title: Input
ctx:
type: object
title: Context
type: object
required:
- loc
- msg
- type
title: ValidationError