> ## Documentation Index
> Fetch the complete documentation index at: https://docs.altur.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Contacts to Campaign

> Crea Contactos de Campaña por lotes. Hasta 1000 contactos por solicitud. Cada item se procesa de forma independiente — una fila inválida no rompe el lote — y se devuelve un `summary` agregado y `results` por item. Usa `if_duplicate` para controlar el comportamiento cuando un contacto ya existe en la campaña (`skip` mantiene el original, `update` sobreescribe los campos mutables).

<Info>
  **Rate Limit:** 12 requests per second
</Info>

Up to **1000 contacts** per request. Each item is processed independently, so one bad row does not fail the batch. The response includes an aggregate `summary` plus per-item `results`.

<Tip>
  Pass an `Idempotency-Key` header (UUID recommended) so retries don't enqueue
  duplicate batches.
</Tip>

## Duplicate Handling

`if_duplicate` controls behavior when the contact's phone number already exists in the campaign:

* `skip` (default): keep the existing contact, return `skipped_duplicate`.
* `update`: overwrite `name`, `context`, `extracted_data`, and `f_id` on the existing contact, return `updated`.

## Per-item Result Statuses

| Status              | Meaning                                                                 |
| :------------------ | :---------------------------------------------------------------------- |
| `created`           | New contact created                                                     |
| `updated`           | Existing contact updated (only when `if_duplicate=update`)              |
| `skipped_duplicate` | Existing contact preserved                                              |
| `failed`            | See `error` (`INVALID_PHONE_NUMBER`, `MISSING_CONTACT`, `SERVER_ERROR`) |

## Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.altur.io/api/v1.0/campaigns/1234/contacts" \
    -H "Authorization: api-key YOUR_API_SECRET_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: c1a45e88-ad34-4f57-9d2e-91a1d4f50f7e" \
    -d '{
      "if_duplicate": "update",
      "contacts": [
        {
          "f_id": "user_12345",
          "name": "John Doe",
          "contact": "+521234567890",
          "context": "Past-due 30 days, owes $1200",
          "extracted_data": { "balance": "1200" }
        },
        {
          "f_id": "user_12346",
          "name": "Sofía Pérez",
          "contact": "+529876543210"
        }
      ]
    }'
  ```

  ```python Python theme={null}
  import requests
  import uuid

  batch = {
      "if_duplicate": "update",
      "contacts": [
          {
              "f_id": "user_12345",
              "name": "John Doe",
              "contact": "+521234567890",
              "context": "Past-due 30 days, owes $1200",
              "extracted_data": {"balance": "1200"},
          },
          {
              "f_id": "user_12346",
              "name": "Sofía Pérez",
              "contact": "+529876543210",
          },
      ],
  }

  response = requests.post(
      "https://api.altur.io/api/v1.0/campaigns/1234/contacts",
      headers={
          "Authorization": "api-key YOUR_API_SECRET_KEY",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json=batch,
  )
  response.raise_for_status()
  body = response.json()
  print(body["summary"])  # {"created": 2, "updated": 0, ...}
  for item in body["results"]:
      if item["status"] == "failed":
          print(item["index"], item["error"], item["contact"])
  ```

  ```javascript JavaScript theme={null}
  const batch = {
    if_duplicate: "update",
    contacts: [
      {
        f_id: "user_12345",
        name: "John Doe",
        contact: "+521234567890",
        context: "Past-due 30 days, owes $1200",
        extracted_data: { balance: "1200" },
      },
      { f_id: "user_12346", name: "Sofía Pérez", contact: "+529876543210" },
    ],
  };

  const res = await fetch(
    "https://api.altur.io/api/v1.0/campaigns/1234/contacts",
    {
      method: "POST",
      headers: {
        Authorization: "api-key YOUR_API_SECRET_KEY",
        "Content-Type": "application/json",
        "Idempotency-Key": crypto.randomUUID(),
      },
      body: JSON.stringify(batch),
    },
  );
  const body = await res.json();
  console.log(body.summary);
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "success": true,
  "summary": { "created": 2, "updated": 0, "skipped_duplicate": 0, "failed": 0 },
  "results": [
    { "index": 0, "status": "created", "id": "42", "contact": "+521234567890" },
    { "index": 1, "status": "created", "id": "43", "contact": "+529876543210" }
  ]
}
```


## OpenAPI

````yaml POST /campaigns/{id}/contacts
openapi: 3.0.1
info:
  title: Altur API
  description: >-
    An API aimed to allow third-party developers to interact with the Altur
    platform through a RESTful interface.
  license:
    name: MIT
  version: '1.0'
servers:
  - url: https://api.altur.io/api/v1.0
security:
  - apiKeyAuth: []
paths:
  /campaigns/{id}/contacts:
    post:
      description: >-
        Batch-creates Campaign Contacts. Up to 1000 contacts per request. Each
        item is processed independently — a single bad row does not fail the
        batch — and an aggregate `summary` plus per-item `results` is returned.
        Use `if_duplicate` to control behavior when a contact already exists in
        the campaign (`skip` keeps the original, `update` overwrites mutable
        fields).
      parameters:
        - name: id
          in: path
          description: The identifier of the Campaign
          schema:
            type: integer
          required: true
        - name: Idempotency-Key
          in: header
          description: >-
            Optional client-generated key (UUID recommended) to make the request
            idempotent.
          schema:
            type: string
          required: false
      requestBody:
        description: Batch of contacts to create
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CampaignContactBatchCreate'
      responses:
        '201':
          description: Batch processed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignContactBatchResponse'
        '400':
          description: Invalid request payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DRFBadRequestError'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Campaign not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignApiError'
components:
  schemas:
    CampaignContactBatchCreate:
      type: object
      required:
        - contacts
      properties:
        contacts:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            type: object
            required:
              - contact
            properties:
              contact:
                type: string
                description: Phone number, normalized to E.164 on insert.
              name:
                type: string
              context:
                type: string
              extracted_data:
                type: object
              f_id:
                type: string
                description: Foreign identifier for the contact in your system.
        if_duplicate:
          type: string
          enum:
            - skip
            - update
          default: skip
          description: >-
            Behavior when the contact's phone number already exists in this
            campaign.
    CampaignContactBatchResponse:
      type: object
      properties:
        success:
          type: boolean
        summary:
          type: object
          properties:
            created:
              type: integer
            updated:
              type: integer
            skipped_duplicate:
              type: integer
            failed:
              type: integer
        results:
          type: array
          items:
            type: object
            properties:
              index:
                type: integer
              status:
                type: string
                enum:
                  - created
                  - updated
                  - skipped_duplicate
                  - failed
              id:
                type: string
                description: >-
                  Set when status is `created`, `updated`, or
                  `skipped_duplicate`.
              contact:
                type: string
              error:
                type: string
                description: >-
                  Set when status is `failed`. One of `INVALID_PHONE_NUMBER`,
                  `MISSING_CONTACT`, `SERVER_ERROR`.
    DRFBadRequestError:
      type: object
      description: >-
        Validation error response where each field name that has an error is a
        property containing an array of error messages
      additionalProperties:
        type: array
        items:
          type: string
      example:
        field1:
          - This field is required.
        field2:
          - Incorrect format.
        non_field_errors:
          - The date range is not valid.
    Error:
      required:
        - error
        - message
      type: object
      properties:
        error:
          type: integer
          format: int32
        message:
          type: string
    CampaignApiError:
      type: object
      description: Standard error envelope returned by the v1.0 campaigns API.
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: Stable, UPPER_SNAKE_CASE error key.
          enum:
            - FORBIDDEN
            - NOT_FOUND
            - RATE_LIMITED
            - SERVER_ERROR
            - INVALID_REQUEST
            - INVALID_STATUS
            - IMMUTABLE_FIELD
            - UNKNOWN_ACTION
            - AGENT_NOT_FOUND
            - INTEGRATION_NOT_FOUND
            - INVALID_INTEGRATION_TYPE
            - INVALID_FILTER_TAG
            - INVALID_PHONE_NUMBER
            - MISSING_CONTACT
            - CONTACT_NOT_FOUND
            - CONTACT_NOT_DELETABLE
            - CAMPAIGN_FINISHED
            - CYCLES_NOT_ENABLED
            - SCHEDULED_MODE_ONLY
            - INVALID_STATUS_TRANSITION
            - BILLING_LIMIT_REACHED
        message:
          type: string
        field:
          type: string
          description: >-
            Set when `error` is `IMMUTABLE_FIELD`; identifies the offending
            field path.
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        Add `api-key YOUR_API_SECRET_KEY` as the value of the `Authorization`
        header.

````