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

# Add a Contact

> Add a person to one of your entities, optionally scoped to one market

Adds a contact to one of your entities. Omit `upstreamDownstreamAssociationId` for an agency-wide contact that every one of your markets can see; supply it to confine the contact to one market relationship.

## Email addresses are unique across Turris, and this is the part to read

Contact email is unique platform-wide, not per agency. So an address you add may already belong to a contact another agency introduced first.

When that happens, this endpoint **attaches a new scope for your entity to the existing contact and returns it as normal**. The response is indistinguishable from having created a fresh contact: it shows only your own scopes, and it reflects the name you sent.

<Note>
  That indistinguishability is deliberate and it is the reason this endpoint does not return **409** for a known address. A 409, or a response carrying somebody else's stored name, would let you test any email against the platform and learn whose contact it is. We would rather the create tell you nothing than tell you that.
</Note>

Two consequences worth planning for:

<CardGroup cols={2}>
  <Card title="Your name is yours only" icon="user">
    On a shared record the stored `firstName` and `lastName` stay as the first agency recorded them — a create must not rename a record two agencies share. You will not see theirs; the response echoes what you sent. Treat it as authoritative for **your** scopes, not as a read of the database.
  </Card>

  <Card title="A duplicate scope is a 409" icon="triangle-exclamation">
    Adding a scope you already hold, for the same entity and market, returns **409**. That is a duplicate rather than a disclosure, and it is actionable.
  </Card>
</CardGroup>

Send an `Idempotency-Key` header. See [Idempotency](/guides/idempotency).


## OpenAPI

````yaml openapi/v2.json POST /v2/downstream/contacts
openapi: 3.0.0
info:
  title: Turris Public API
  description: API for managing insurance compliance data
  version: 2.0.0
  contact: {}
servers:
  - url: https://public.api.live.turrisfi.com
    description: Production
  - url: https://public.api.sandbox.turrisfi.com
    description: Sandbox
security: []
tags: []
paths:
  /v2/downstream/contacts:
    post:
      tags:
        - downstream/contacts
      summary: Add a contact
      description: >-
        Adds a contact to one of your entities, optionally confined to a single
        market. Email addresses are unique across Turris, so an address already
        known to the platform is attached to your organization as an additional
        scope rather than duplicated or rejected. In that case the stored first
        and last name are left as they are, because the record may be shared,
        and the response echoes the name you sent rather than the stored one.
        The response shows only your own scopes. Adding a scope you already hold
        returns 409.
      operationId: ContactsController_addContact_v2
      parameters:
        - name: idempotency-key
          in: header
          description: UUID to ensure idempotent request processing
          required: false
          schema:
            type: string
            example: 550e8400-e29b-41d4-a716-446655440000
        - name: x-idempotency-key
          in: header
          description: Alternative UUID header for idempotent request processing
          required: false
          schema:
            type: string
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddContactDto'
      responses:
        '201':
          description: The contact, with your scopes
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/DownstreamContactResponse'
                  requestId:
                    type: string
                    description: Unique request identifier
                    example: dev-2c5e7cf2-9acf-4c8c-ab2f-b81f39d775a8
                  timestamp:
                    type: string
                    description: Response timestamp
                    example: '2025-11-12T20:49:03.293Z'
                required:
                  - data
                  - requestId
                  - timestamp
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '401':
          description: Invalid or missing auth token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '403':
          description: Your organization is not entitled to the public API
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '404':
          description: >-
            downstreamEntityId or upstreamDownstreamAssociationId is not in your
            organization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '409':
          description: The contact already has that scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
components:
  schemas:
    AddContactDto:
      type: object
      properties:
        firstName:
          type: string
          description: >-
            Given name. Stored only when this email is new to Turris; on an
            address the platform already knows, the existing record keeps its
            name and this value is echoed back to you unchanged.
          example: Jon
        lastName:
          type: string
          description: >-
            Surname. Stored only when this email is new to Turris; on an address
            the platform already knows, the existing record keeps its name and
            this value is echoed back to you unchanged.
          example: Doe
        email:
          type: string
          description: >-
            Email address. Unique across Turris and matched case-insensitively,
            so an address already known to the platform is attached to your
            agency rather than duplicated.
          example: example@test.com
        downstreamEntityId:
          type: string
          description: >-
            Which of your entities this contact belongs to. Defaults to your own
            organization.
          example: 6610b3d2c2e0a51b8c0d1f02
        upstreamDownstreamAssociationId:
          type: string
          description: >-
            Confine the contact to one market relationship. Omit for an
            agency-wide contact, which every one of your markets can see.
          example: 6627f1a9c2e0a51b8c0d4e3a
        businessRoles:
          type: array
          description: Business roles this contact holds within the scope being created.
          example:
            - Compliance Officer
          items:
            type: string
            enum:
              - Compliance Officer
              - Principal Owner
              - Account Manager
              - Finance Manager
              - Support Staff
              - Contract Signatory
              - Accounts Payable
              - Policy Maintenance
        isPrimary:
          type: boolean
          description: Whether this is the primary contact for the scope being created.
          example: false
      required:
        - firstName
        - lastName
        - email
    DownstreamContactResponse:
      type: object
      properties:
        contactId:
          type: string
          description: Unique identifier of the contact
          example: 6627f2b5c2e0a51b8c0d4e7c
        firstName:
          type: string
          description: Given name
          example: Jon
        lastName:
          type: string
          description: Surname
          example: Doe
        email:
          type: string
          description: Email address
          example: example@test.com
        isDeliverable:
          type: boolean
          description: Whether the email address is deliverable
          example: true
        scopes:
          description: >-
            The scopes inside your organization. Scopes belonging to other
            agencies are not returned.
          type: array
          items:
            $ref: '#/components/schemas/DownstreamContactScopeResponse'
        createdAt:
          type: string
          description: ISO 8601 creation timestamp
          example: '2025-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          description: ISO 8601 last-update timestamp
          example: '2025-06-02T08:12:44.000Z'
      required:
        - contactId
        - firstName
        - lastName
        - email
        - isDeliverable
        - scopes
    ErrorResponseDto:
      type: object
      properties:
        statusCode:
          type: number
          description: HTTP status code
        requestId:
          type: string
          description: Unique request identifier for debugging
        errorType:
          type: string
          description: >-
            Machine-readable error classification. Branch on this rather than on
            `errorMessage`, which is prose and may change. Authentication
            failures returned by our identity provider are forwarded verbatim,
            so a 401 can carry a code outside this list; treat an unrecognised
            value as a generic failure of its HTTP status.
          enum:
            - conflict
            - contact_not_authorized
            - document_exceeds_page_limit
            - downstream_entity_member_exists
            - duplicate_external_id_error
            - duplicate_member_email
            - duplicate_producer_code_error
            - forbidden
            - gateway_timeout
            - inactive_email
            - internal_server_error
            - invalid_email
            - invalid_email_for_invites
            - invalid_organization_category
            - invalid_organization_slug
            - invalid_phone_number
            - invalid_token
            - invite_limit_reached
            - jwt_invalid
            - m2m_client_not_found
            - not_found
            - organization_already_exists
            - organization_slug_already_used
            - payment_required
            - producer_agreement_required
            - product_feature_subscription_required
            - service_unavailable
            - session_authorization_error
            - some_custom_error_string
            - throttled
            - too_many_requests
            - unauthorized
            - unauthorized_client
            - unexpected_400_stytch_error
            - unexpected_403_stytch_error
            - unexpected_404_stytch_error
            - unexpected_error
            - unexpected_stytch_error
            - unprocessable_entity
            - validation_error
          example: validation_error
        errorMessage:
          description: Array of error messages
          type: array
          items:
            type: string
        timestamp:
          type: string
          description: ISO timestamp when the error occurred
        details:
          type: object
          description: Additional error context
      required:
        - statusCode
        - requestId
        - errorType
        - errorMessage
        - timestamp
    DownstreamContactScopeResponse:
      type: object
      properties:
        downstreamEntityId:
          type: string
          description: The entity this scope belongs to
          example: 6610b3d2c2e0a51b8c0d1f02
        upstreamDownstreamAssociationId:
          type: string
          description: >-
            The market relationship this scope is confined to. Absent means the
            contact is visible to every market that entity deals with, which is
            how agency-created contacts are stored.
          example: 6627f1a9c2e0a51b8c0d4e3a
        businessRoles:
          type: array
          description: Business roles this contact holds under this scope
          example:
            - Finance Manager
          items:
            type: string
            enum:
              - Compliance Officer
              - Principal Owner
              - Account Manager
              - Finance Manager
              - Support Staff
              - Contract Signatory
              - Accounts Payable
              - Policy Maintenance
        isPrimary:
          type: boolean
          description: Whether this contact is a primary contact under this scope
          example: true
      required:
        - downstreamEntityId
        - businessRoles
        - isPrimary

````

## Related topics

- [Add (Dangerously)](/api-reference/v1/downstream-entity-associations/dangerously-add.md)
- [Add a Producer](/api-reference/v2/downstream/producers/add-producer.md)
- [Add Downstream Entity](/api-reference/v1/downstream-entity-associations/add.md)
