C2C Public API (v1)
Overview
The C2C (tenant public) API is a versioned, JSON API scoped per tenant. All routes under /api/c2c share the same authentication, API version validation, and request logging.
Base URL pattern:
https://{your-tenant-host}/api/c2cExample: https://mytenant.click2close.io/api/c2c
Full endpoint URLs are base URL + path, e.g. POST https://{tenant-host}/api/c2c/contacts/assets.
Required headers (all C2C endpoints)
Header | Required | Description |
|---|---|---|
| Yes | Must be |
API key | Yes | See Authentication. Missing or invalid → 401 |
| Yes (for JSON bodies) | Must be |
Authentication
Provide exactly one of:
Authorization: Bearer c2c_{secret}— full key including thec2c_prefix.X-Api-Key: c2c_{secret}— same string as the Bearer token.
API keys are tenant-scoped and issued in the product. The plaintext key must start with c2c_.
Available endpoints
Method | Path | Description |
|---|---|---|
|
| Health check for the C2C stack on this tenant. |
|
| Attach one or more base64-encoded files to an existing contact (identified by email). |
GET /api/c2c/health
Success: HTTP 200
{
"data": {
"status": "ok"
}
}
Use this to verify DNS, TLS, X-Api-Version, and API key before heavier integrations.
POST /api/c2c/contacts/assets
Uploads files as base64, decodes them on the server, and stores them as ** Media Library** assets on the Contact.
Purpose
Target contact must already exist (matched by email).
Attach 1–10 files per request; each file must pass MIME allowlist, size limits, and content-vs-declared-MIME checks.
Request
Headers
X-Api-Version: v1Content-Type: application/jsonAuthorization: Bearer c2c_…orX-Api-Key: c2c_…
Body (JSON)
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | Contact email. Lookup is case-insensitive ( |
|
| Yes | Min 1, max 10 objects. |
Each element of files
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | Max 255 characters. Logical file name (basename used if a path is given). |
|
| Yes | Must be one of the allowed MIME types listed below. Compared to detected MIME from decoded bytes ( |
|
| Yes | File bytes base64-encoded. Optional data URI prefix supported: |
|
| No | Max 255. Optional accessibility text stored on the media record. |
|
| No | Max 1000. Optional description stored on the media record. |
Allowed mime_type values
Images:
image/jpeg,image/png,image/webp,image/gif,image/svg+xmlPDF:
application/pdfSpreadsheets / CSV:
text/csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheetPlain text:
text/plain
Size limits
Limit | Value |
|---|---|
Per file (decoded binary) | ≤ 25 MB |
Total across all | ≤ 50 MB |
MIME aliases
The server may accept a declared mime_type if detected MIME matches known aliases (for example: XLS/XLSX sometimes detected as application/octet-stream; CSV as text/plain). Exact equality between declared and detected MIME also passes.
Processing flow
Validate JSON and custom rules (base64, sizes, MIME allowlist, MIME vs content).
Resolve Contact with
LOWER(email) = lower(trim(email)). If none → 422contact_not_found.For each file in array order:
Decode base64 → write temp file.
Raster images (not SVG) whose original extension is not
gifare converted to WebP before save (aligned with internal media upload behavior). GIF and SVG are not converted through that path.Attach media to contact
defaultcollection,publicon tenant disk.
Return 201 with contact + created asset metadata.
Unexpected server errors during upload → 500
store_failed(logged server-side).
Success response
HTTP 201 Created
{
"data": {
"contact": {
"uuid": "<contact_uuid>",
"email": "<email_as_stored>"
},
"assets": [
{
"uuid": "<media_uuid>",
"name": "<stored_name>",
"mime_type": "<mime_type>",
"type": "image|file",
"size": 12345,
"created_at": "YYYY-mm-dd HH:ii:ss"
}
]
}
}
type:imageif stored MIME is underimage/*, otherwisefile.name: May differ from requestfilename(e.g. WebP conversion changes extension for eligible images).
Error responses
Standard envelope:
{
"error": "<code>",
"message": "<message>"
}
Validation (422):
{
"error": "validation_failed",
"message": "<message>",
"errors": {}
}
HTTP |
| Typical cause |
|---|---|---|
400 |
|
|
400 |
| Version other than |
401 |
| Missing/invalid key or wrong prefix |
422 |
| Schema, base64, size, MIME allowlist, or MIME/content mismatch |
422 |
| No contact for given email |
500 |
| Unexpected error while storing media |
User-visible message values may be localized (en / es) depending on server configuration (see resources/lang/*/api_error.php, key c2c_contact_asset).
Example (curl)
curl --request POST \
--url 'https://{tenant-host}/api/c2c/contacts/assets' \
--header 'Authorization: Bearer c2c_YOUR_KEY' \
--header 'Content-Type: application/json' \
--header 'X-Api-Version: v1' \
--data '{
"email": "user@example.com",
"files": [
{
"filename": "document.pdf",
"mime_type": "application/pdf",
"content": "<base64_here>"
}
]
}'
Integrator notes
The API does not create contacts; ensure the contact exists first.
Each successful call adds media rows; it does not replace previous uploads unless handled elsewhere in your stack.
All C2C routes are under
/api/c2c/on your tenant API host.