Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/politicalcomms-com:politicalcomms-com-media-files-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Political Comms Media Files API
summary: Direct-to-carrier political texting API for campaigns, PACs, advocacy organizations, fundraisers, and elected officials.
description: 'Public REST API for the Political Comms platform. Surfaces include Projects (compose, test, schedule, send), Conversations (read inbound threads and reply inside them), Contact Lists (S3 import and analysis), Media Files, Organizations and hierarchy, Brands, Campaigns, Tracking Domains, Phone Numbers, Analytics, and Billing.
Authentication is an API key passed in the `X-API-Key` header. Keys are generated from the dashboard at Admin → API and are prefixed `pc_live_`. All POST and PATCH endpoints that mutate state are designed to be safe to retry, with optional `Idempotency-Key` headers for stronger guarantees. Rate limit is 600 requests per minute per key (bursts up to 600, refilling at 10 per second, the same for every scope; the `X-RateLimit-Limit` header reports your key''s exact limit, and `Retry-After` on a 429 is the seconds until your next request is allowed).
Webhooks emit `message.sent`, `message.delivered`, `message.failed`, `message.replied`, and `link.clicked` events. Payloads are HMAC-signed; validate the signature before trusting any payload.
A Model Context Protocol (MCP) server is available at https://docs.politicalcomms.com/mcp for AI agents that need to search the documentation programmatically. The developer hub at https://politicalcomms.com/developers/ has quickstart examples in cURL, raw HTTP, and Python.'
version: 1.4.1
termsOfService: https://politicalcomms.com/terms/
contact:
name: Political Comms Support
email: support@politicalcomms.com
url: https://docs.politicalcomms.com
license:
name: Proprietary
url: https://politicalcomms.com/terms/
x-logo:
url: https://politicalcomms.com/images/brand/pcomms-logo-left-of-text.png
altText: Political Comms
backgroundColor: '#ffffff'
href: https://politicalcomms.com/
x-mcp:
url: https://docs.politicalcomms.com/mcp
discovery_url: https://docs.politicalcomms.com/.well-known/mcp
transport: http
auth: none
tools:
- search_political_comms
- query_docs_filesystem_political_comms
- submit_feedback
servers:
- url: https://api.politicalcomms.com/v1
description: Production
security:
- ApiKeyAuth: []
tags:
- name: Media Files
description: List, import, and fetch media files.
paths:
/media:
get:
tags:
- Media Files
summary: List Media Files
description: List all media files. Filter by organization or brand.
operationId: listMedia
parameters:
- $ref: '#/components/parameters/OrganizationId'
- $ref: '#/components/parameters/BrandId'
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
brand_id:
type:
- string
- 'null'
brand_name:
type:
- string
- 'null'
org_id:
type: string
org_name:
type: string
created_at:
type: string
format: date-time
url:
type:
- string
- 'null'
description: 'Permanent URL of the stored file. Populated only when `status` is `ready`, and null while the file is still `optimizing` or after a failure. Stable for the life of the file: a stored file is never re-processed or rewritten, so this URL always returns the same bytes. Treat it as opaque, since the filename can differ from `name` (video is converted to `.mp4`).'
storage_key:
type:
- string
- 'null'
description: Internal object key. Null until `status` is `ready`.
file_size_bytes:
type: integer
description: Size of the stored file in bytes. On rows that are not yet `ready`, this is the size as submitted rather than the stored size.
content_type:
type:
- string
- 'null'
description: Inferred from the file extension; null when unknown.
status:
type: string
enum:
- optimizing
- ready
- failed
uploaded_via_api:
type: boolean
additionalProperties: true
required:
- success
- data
additionalProperties: true
example:
success: true
data:
- id: 5d2b8f4a-7c31-4e9d-b6a8-3f9c1e5d7a20
name: rally-photo.jpg
brand_id: 7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64
brand_name: Smith PAC
org_id: 0d9f6a2e-1b34-4c8e-9f21-3e7a8b5c4d10
org_name: Smith Campaign 2024
created_at: '2025-04-02T15:30:00.000Z'
url: https://media.politicalcomms.com/0d9f6a2e-1b34-4c8e-9f21-3e7a8b5c4d10/7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64/rally-photo_image_5d2b8f4a.jpg
storage_key: 0d9f6a2e-1b34-4c8e-9f21-3e7a8b5c4d10/7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64/rally-photo_image_5d2b8f4a.jpg
file_size_bytes: 482133
content_type: image/jpeg
status: ready
uploaded_via_api: true
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
post:
tags:
- Media Files
summary: Import Media
description: 'Asynchronous job: provide an HTTPS URL to the file and the server retrieves and stores it. Returns immediately; poll `GET /media/{id}` until `status` is `ready`.'
operationId: importMedia
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- source_url
properties:
source_url:
type: string
format: uri
organization_id:
type: string
description: Target organization for the media file. Defaults to the organization that owns the API key. Send it to target a sub-organization under an agency-tier parent; the API key is always limited to its own organization tree (otherwise the request returns 403 ORG_ACCESS_DENIED).
brand_id:
type: string
description: Optional. When provided, the media file is scoped to this brand and the brand's organization is used, so a brand in an accessible sub-organization works without `organization_id`. A brand that does not belong to an organization your API key can access returns 404 BRAND_NOT_FOUND. If both are sent and the brand belongs to a different accessible organization than `organization_id`, the request returns 400 BRAND_ORG_MISMATCH.
name:
type: string
usage:
type: string
enum:
- mms
- email_asset
default: mms
description: Which library the file lands in. `mms` is a texting attachment. `email_asset` is an image for the email template library, which is what Lincoln drafting and the document editor read from. Email assets are organization-scoped, so `brand_id` must be omitted when `usage` is `email_asset`.
example:
source_url: https://example.com/path/to/image.jpg
organization_id: 01HX0000000000000000000000
brand_id: 01HX0000000000000000000001
name: spring-outreach-hero
usage: mms
responses:
'202':
description: Import accepted and optimizing asynchronously. Poll GET /media/{id} for readiness.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
media_id:
type: string
status:
type: string
enum:
- optimizing
uploaded_via_api:
type: boolean
file_size_bytes:
type: integer
name:
type: string
additionalProperties: true
required:
- success
- data
additionalProperties: true
example:
success: true
data:
media_id: 5d2b8f4a-7c31-4e9d-b6a8-3f9c1e5d7a20
status: optimizing
uploaded_via_api: true
file_size_bytes: 482133
name: rally-photo.jpg
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
parameters:
- $ref: '#/components/parameters/IdempotencyKeyHeader'
/media/{id}:
get:
tags:
- Media Files
summary: Get Media File
description: Fetch a single media file including optimization status and metadata.
operationId: getMedia
parameters:
- $ref: '#/components/parameters/IdPath'
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
media_id:
type: string
organization_id:
type:
- string
- 'null'
brand_id:
type:
- string
- 'null'
name:
type: string
status:
type: string
enum:
- optimizing
- ready
- failed
content_type:
type:
- string
- 'null'
description: Inferred from the file extension; null when unknown.
file_size_bytes:
type: integer
description: 0 until the file has been fetched and measured.
storage_key:
type:
- string
- 'null'
url:
type:
- string
- 'null'
description: 'Permanent URL of the stored file. Populated only when `status` is `ready`, and null while the file is still `optimizing` or after a failure. Stable for the life of the file: a stored file is never re-processed or rewritten, so this URL always returns the same bytes. Treat it as opaque, since the filename can differ from `name` (video is converted to `.mp4`).'
uploaded_via_api:
type: boolean
created_at:
type: string
format: date-time
optimized_at:
type:
- string
- 'null'
additionalProperties: true
required:
- success
- data
additionalProperties: true
example:
success: true
data:
media_id: 5d2b8f4a-7c31-4e9d-b6a8-3f9c1e5d7a20
organization_id: 0d9f6a2e-1b34-4c8e-9f21-3e7a8b5c4d10
brand_id: 7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64
name: rally-photo.jpg
status: ready
content_type: image/jpeg
file_size_bytes: 482133
storage_key: 0d9f6a2e-1b34-4c8e-9f21-3e7a8b5c4d10/7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64/rally-photo_image_5d2b8f4a.jpg
url: https://media.politicalcomms.com/0d9f6a2e-1b34-4c8e-9f21-3e7a8b5c4d10/7c1de5f0-9a42-4b6d-8e33-2f5a9c7b1e64/rally-photo_image_5d2b8f4a.jpg
uploaded_via_api: true
created_at: '2025-04-02T15:30:00.000Z'
optimized_at: '2025-04-02T15:30:12.000Z'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
delete:
tags:
- Media Files
summary: Delete Media File
description: Permanently remove a media file from your account. The file is detached from any draft projects or survey questions that reference it and no longer appears in list or read endpoints. Already-sent MMS messages keep rendering their media. Deletion is blocked with a 409 while a scheduled, sending, or active project uses the file; unschedule the project or wait for the send to finish, then retry.
operationId: deleteMedia
parameters:
- $ref: '#/components/parameters/IdPath'
- $ref: '#/components/parameters/IdempotencyKeyHeader'
responses:
'200':
description: Media file deleted.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
data:
type: object
properties:
media_id:
type: string
name:
type: string
deleted:
type: boolean
additionalProperties: true
required:
- success
- data
additionalProperties: true
example:
success: true
data:
media_id: 9c2e4f8a-1d37-4b52-8e69-3a5c7d9f1e84
name: rally-flyer.jpg
deleted: true
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: The media file is used by a project that is scheduled, sending, or active. Error code MEDIA_IN_USE; `details.projects` lists the blocking projects as `{id, name, status}`.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
components:
responses:
Forbidden:
description: The API key lacks the required permission scope, or the requested resource belongs to an organization outside the key's hierarchy.
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/AuthErrorResponse'
- $ref: '#/components/schemas/ErrorResponse'
examples:
missingScope:
summary: Key lacks the required permission scope
value:
error: Insufficient permissions
message: 'This API key does not have the required permissions: public:read'
orgAccessDenied:
summary: Resource outside the key's organization hierarchy
value:
error: 'Access denied: You do not have permission to access this organization''s data'
code: ORG_ACCESS_DENIED
correlationId: de038205-7c9c-4d4a-99bc-d7275a52f07e
BadRequest:
description: Invalid request (validation failure, malformed parameters, or a request the resource state does not allow).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error: Validation failed
code: VALIDATION_ERROR
correlationId: 030bf857-922f-4b82-8fca-c5b0769c1590
details:
- field: organization_id
message: Invalid UUID
code: invalid_format
Unauthorized:
description: Missing, malformed, revoked, or expired API key.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthErrorResponse'
example:
error: API key required
message: Please provide an API key in the X-API-Key header
NotFound:
description: Resource not found or not visible to this API key.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error: Project not found
code: PROJECT_NOT_FOUND
correlationId: de038205-7c9c-4d4a-99bc-d7275a52f07e
RateLimited:
description: Rate limit exceeded. Check the Retry-After header.
headers:
Retry-After:
description: Seconds until the window resets.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/RateLimitErrorResponse'
example:
success: false
error: Rate limit exceeded
message: Maximum 600 requests per 60 seconds
code: RATE_LIMIT_EXCEEDED
statusCode: 429
retryAfter: 3
InternalError:
description: Unexpected server error.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error: An unexpected error occurred. Please try again later.
code: INTERNAL_ERROR
correlationId: de038205-7c9c-4d4a-99bc-d7275a52f07e
parameters:
IdPath:
name: id
in: path
required: true
description: Resource ID
schema:
type: string
BrandId:
name: brand_id
in: query
required: false
description: Filter to a specific brand
schema:
type: string
OrganizationId:
name: organization_id
in: query
required: false
description: Filter to a specific descendant organization
schema:
type: string
IdempotencyKeyHeader:
name: Idempotency-Key
in: header
required: false
schema:
type: string
minLength: 16
maxLength: 200
description: 'Optional idempotency key: a unique string of 16-200 printable ASCII characters (a UUID is recommended). Retrying the write with the same key within 24 hours returns the stored response of the first call with an `X-Idempotent-Replayed: true` response header instead of executing it again. Reusing a key with a different request body returns `422` (`IDEMPOTENCY_MISMATCH`); a duplicate sent while the first call is still running returns `409` with a `Retry-After` header. Keys are scoped per endpoint and organization.'
schemas:
RateLimitErrorResponse:
type: object
properties:
success:
type: boolean
enum:
- false
error:
type: string
message:
type: string
code:
type: string
enum:
- RATE_LIMIT_EXCEEDED
statusCode:
type: integer
enum:
- 429
retryAfter:
type: integer
description: Seconds until the rate-limit window resets. Also sent as the Retry-After header.
required:
- success
- error
- code
- statusCode
- retryAfter
additionalProperties: true
AuthErrorResponse:
type: object
properties:
error:
type: string
description: Short error summary.
message:
type: string
description: Human-readable detail.
required:
- error
- message
additionalProperties: true
description: Error body returned by the API-key authentication layer (401 and permission 403s).
ErrorResponse:
type: object
properties:
error:
type: string
description: Human-readable error message.
code:
type: string
description: Machine-readable error code, e.g. VALIDATION_ERROR, ORG_ACCESS_DENIED, PROJECT_NOT_FOUND.
correlationId:
type: string
description: Request correlation ID; include it in support requests.
details:
type:
- array
- object
description: Present on some errors. For VALIDATION_ERROR, an array of { field, message, code } with one entry per rejected field (field is the dotted path); an object for other codes (e.g. insufficient-balance shortfall).
required:
- error
- code
additionalProperties: true
description: Standard error body produced by the API error handler for 4xx/5xx responses.
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: Authenticate every request by passing your API key in the X-API-Key header. Keys are scoped to your organization hierarchy.