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

# Assign a Contact to a Proposal

> POST /public/proposals/{ProposalId}/contact/{ContactId} — Links a contact to a proposal. May auto-assign location and trigger tax recalculations.

Use this endpoint to associate an existing contact with a proposal. Both the proposal and contact must belong to the same account. If the contact has exactly one primary location, the API automatically assigns that location to the proposal at the same time. When a location is set — either automatically here or explicitly via the assign-location endpoint — tax calculations are recalculated based on the contact's location data.

<Note>
  A proposal must have a contact assigned before you can assign a location. Attempting to assign a location to a proposal with no contact returns 409.
</Note>

<RequestExample>
  ```bash curl theme={null}
  curl -i -X POST \
    'https://api.portal.io/public/proposals/123/contact/456' \
    -H 'Accept: application/json' \
    -H 'Content-Type: application/json' \
    -H 'X-MSS-API-APPID: YOUR_APP_ID' \
    -H 'X-MSS-API-USERKEY: YOUR_USER_KEY' \
    -H 'X-MSS-CUSTOM-DATE: Mon, 06 Apr 2026 00:22:19 GMT' \
    -H 'X-MSS-SIGNATURE: BASE64_SIGNATURE' \
    -d '{"proposalId": 123, "contactId": 456}'
  ```

  ```python python theme={null}
  import requests
  from portal_auth import sign_request

  url = "https://api.portal.io/public/proposals/123/contact/456"
  timestamp = "Mon, 06 Apr 2026 00:22:19 GMT"
  content_type = "application/json"
  signature = sign_request("POST", url, content_type, timestamp, "YOUR_USER_KEY", "YOUR_SECRET_KEY")

  response = requests.post(url, headers={
      "Accept": "application/json",
      "Content-Type": content_type,
      "X-MSS-API-APPID": "YOUR_APP_ID",
      "X-MSS-API-USERKEY": "YOUR_USER_KEY",
      "X-MSS-CUSTOM-DATE": timestamp,
      "X-MSS-SIGNATURE": signature
  }, json={"proposalId": 123, "contactId": 456})
  print(response.status_code)  # 200 OK on success
  ```

  ```javascript node.js theme={null}
  const { signRequest } = require('./portalAuth');

  const url = "https://api.portal.io/public/proposals/123/contact/456";
  const timestamp = new Date().toUTCString();
  const contentType = "application/json";
  const signature = signRequest("POST", url, contentType, timestamp, "YOUR_USER_KEY", "YOUR_SECRET_KEY");

  const response = await fetch(url, {
      method: "POST",
      headers: {
          "Accept": "application/json",
          "Content-Type": contentType,
          "X-MSS-API-APPID": "YOUR_APP_ID",
          "X-MSS-API-USERKEY": "YOUR_USER_KEY",
          "X-MSS-CUSTOM-DATE": timestamp,
          "X-MSS-SIGNATURE": signature
      },
      body: JSON.stringify({ proposalId: 123, contactId: 456 })
  });
  console.log(response.status);  // 200 OK on success
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  // No content returned on success
  ```
</ResponseExample>


## OpenAPI

````yaml POST /public/proposals/{ProposalId}/contact/{ContactId}
openapi: 3.1.0
info:
  title: Portal.Api
  version: '1.0'
servers:
  - url: http://127.0.0.1:5000
security: []
tags: []
paths:
  /public/proposals/{ProposalId}/contact/{ContactId}:
    post:
      tags:
        - Proposals
      summary: Assign Contact to Proposal
      description: >-
        Assigns an existing contact (person/client) to a proposal. The proposal
        and contact must belong to the same account. If the contact has a single
        primary location, that location is automatically assigned to the
        proposal as well. Assigning a contact may trigger tax recalculations
        based on the contact's location.
      operationId: AssignProposalContact
      parameters:
        - $ref: '#/components/parameters/Accept'
        - name: X-MSS-API-APPID
          in: header
          description: Application Id
          required: true
          schema:
            type: string
        - name: X-MSS-CUSTOM-DATE
          in: header
          description: A date timestamp of the request
          required: true
          schema:
            type: string
        - name: X-MSS-SIGNATURE
          in: header
          description: A signature for the request
          required: true
          schema:
            type: string
        - name: X-MSS-API-USERKEY
          in: header
          description: User API Key
          required: true
          schema:
            type: string
        - name: ProposalId
          in: path
          description: Proposal Id.
          required: true
          schema:
            type: integer
            format: int32
        - name: ContactId
          in: path
          description: Contact Id.
          required: true
          schema:
            type: integer
            format: int32
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicProposalAssignContactRequest'
      responses:
        '204':
          description: No Content
        '401':
          description: >-
            Not Authorized. Ensure a valid session cookie or HMAC authentication
            headers are provided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
        '402':
          description: >-
            The dealer's subscription is inactive or expired. An active
            subscription is required to use this endpoint.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
        '403':
          description: You do not have permission for this action.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
        '404':
          description: Proposal or contact not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HttpError'
      deprecated: false
      x-codeSamples: []
components:
  parameters:
    Accept:
      name: Accept
      in: header
      description: Accept Header
      required: true
      schema:
        type: string
        enum:
          - application/json
  schemas:
    PublicProposalAssignContactRequest:
      title: PublicProposalAssignContactRequest
      required:
        - proposalId
        - contactId
      properties:
        proposalId:
          description: Proposal Id.
          type: integer
          format: int32
        contactId:
          description: Contact Id.
          type: integer
          format: int32
      description: Proposals Service
      type: object
    HttpError:
      title: HttpError
      properties:
        errorCode:
          type: string
        stackTrace:
          type: string
        contentType:
          type: string
        headers:
          $ref: '#/components/schemas/Dictionary_String_String_'
        cookies:
          type: array
          items:
            $ref: '#/components/schemas/Cookie'
        status:
          type: integer
          format: int32
        statusCode:
          type: string
        statusDescription:
          type: string
        response:
          $ref: '#/components/schemas/Object'
        responseFilter:
          $ref: '#/components/schemas/IContentTypeWriter'
        requestContext:
          $ref: '#/components/schemas/IRequest'
        paddingLength:
          type: integer
          format: int32
        resultScope:
          $ref: '#/components/schemas/Func_IDisposable_'
        options:
          $ref: '#/components/schemas/IDictionary_String_String_'
        responseStatus:
          $ref: '#/components/schemas/ResponseStatus'
        targetSite:
          $ref: '#/components/schemas/MethodBase'
        message:
          type: string
        data:
          $ref: '#/components/schemas/IDictionary'
        innerException:
          $ref: '#/components/schemas/Exception'
        helpLink:
          type: string
        source:
          type: string
        hResult:
          type: integer
          format: int32
      description: HttpError
      type: object
    Dictionary_String_String_:
      title: Dictionary<String,String>
      additionalProperties:
        type: string
      description: Dictionary<String,String>
      type: object
    Cookie:
      title: Cookie
      properties: {}
      description: Cookie
      type: object
    Object:
      properties: {}
      description: Object
      type: object
    IContentTypeWriter:
      title: IContentTypeWriter
      properties: {}
      description: IContentTypeWriter
      type: object
    IRequest:
      title: IRequest
      properties: {}
      description: IRequest
      type: object
    Func_IDisposable_:
      title: Func`1
      properties: {}
      description: Func<IDisposable>
      type: object
    IDictionary_String_String_:
      title: IDictionary<String,String>
      additionalProperties:
        type: string
      description: IDictionary<String,String>
      type: object
    ResponseStatus:
      title: ResponseStatus
      properties:
        errorCode:
          type: string
        message:
          type: string
        stackTrace:
          type: string
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ResponseError'
        meta:
          $ref: '#/components/schemas/Dictionary_String_String_'
      description: ResponseStatus
      type: object
    MethodBase:
      title: MethodBase
      properties: {}
      description: MethodBase
      type: object
    IDictionary:
      title: IDictionary
      properties: {}
      description: IDictionary
      type: object
    Exception:
      title: Exception
      properties: {}
      description: Exception
      type: object
    ResponseError:
      title: ResponseError
      properties:
        errorCode:
          type: string
        fieldName:
          type: string
        message:
          type: string
        meta:
          $ref: '#/components/schemas/Dictionary_String_String_'
      description: ResponseError
      type: object

````