Skip to main content
Contacts in Portal.io represent the people and companies in your dealer account — the clients you build proposals for. Each contact stores identifying information, communication details, and one or more physical locations. Locations serve two purposes: they let you identify where a job takes place, and they drive tax calculations on proposals.

What is a contact?

A contact (referred to as a “person” in the API) is a record that stores information about an individual or a company. Every contact has a partyType that controls how it is classified:
partyTypeDescription
PersonAn individual person. Requires firstName.
CompanyA company or organization. Requires companyName.
Contacts also have a contactType that categorizes their relationship to your business:
contactTypeDescription
ClientA paying customer.
EmployeeA member of your team.
ContractorA subcontractor.
OtherAny other relationship.

Contact fields

FieldDescription
idUnique numeric identifier for the contact.
partyTypePerson or Company.
contactTypeCategory of the contact relationship.
firstNameFirst name (required for individuals).
lastNameLast name.
companyNameCompany name (required when partyType is Company).
contactEmailPrimary email address.
contactEmailCCAdditional email addresses copied when a proposal is sent.
contactPhonePrimary phone number.

Locations

Each contact can have one or more locations (physical addresses). A location stores:
  • Street address, suite, city, postal code, state, and country
  • A contact name and phone number for that location
  • Flags for isPrimary (the contact’s main address) and isBilling (used for billing)
Locations are returned with the primaryLocation and billingLocation fields on the contact detail response, and you can also retrieve the full list via GET /public/people/{ContactId}/location.

Why locations matter for proposals

Assigning a location to a proposal is how Portal.io determines the applicable sales tax rates. Portal.io uses the ClientLocation method: it looks up tax rates for the address and applies them to the taxable parts of the proposal. Until you assign a location, tax totals on the proposal remain zero.
You must assign a contact to a proposal before you can assign a location. Attempting to assign a location to a proposal with no contact returns a 409 Conflict.

How contacts relate to proposals

A proposal can have one assigned contact and one assigned location. The relationship works like this:
  • You assign a contact using POST /public/proposals/{ProposalId}/contact/{ContactId}.
  • If the contact has exactly one primary location, Portal.io automatically assigns that location to the proposal as well.
  • If the contact has multiple locations, you assign the location separately using POST /public/proposals/{ProposalId}/location/{LocationId}. The location must belong to the already-assigned contact.
  • Assigning or changing a location triggers an immediate tax recalculation on the proposal.

Creating contacts and locations

When you create a new contact, PartyType, ContactType, and FirstName are required. CompanyName is also required when PartyType is Company.
curl -X POST https://api.portal.io/public/people \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d PartyType=Person \
  -d ContactType=Client \
  -d FirstName=Jane \
  -d LastName=Smith \
  -d ContactEmail=jane@example.com
To add a location to the contact, use POST /public/people/{ContactId}/location. Street is the only required field, but if you supply a Country, State is also required.
curl -X POST https://api.portal.io/public/people/42/location \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d Street="123+Main+St" \
  -d City=Austin \
  -d State=TX \
  -d PostalCode=78701 \
  -d Country=US \
  -d IsPrimary=true

Searching for contacts

The GET /public/people endpoint returns a paged list of contacts for your account. You can filter by SearchText and ContactTypes, and control pagination with PageNumber and PageSize (both default to 1 and 10 respectively).
curl -X GET 'https://api.portal.io/public/people?SearchText=Smith&ContactTypes=Client' \
  -H 'Accept: application/json'

API operations reference

Search contacts

GET /public/peopleReturns a paged list of contacts. Supports search text, contact type filter, sort, and pagination.

Create contact

POST /public/peopleCreates a new contact in your account.

Get contact details

GET /public/people/{ContactId}Returns full contact details including primary and billing locations. Pass IncludeCounts=true to include proposal and payment counts.

List contact locations

GET /public/people/{ContactId}/locationReturns all locations for a contact, ordered with primary first, then billing, then most recently modified.

Create location

POST /public/people/{ContactId}/locationAdds a new location to an existing contact.

Assign contact to proposal

POST /public/proposals/{ProposalId}/contact/{ContactId}Links a contact to a proposal. Auto-assigns the location if the contact has one primary location.

Assign location to proposal

POST /public/proposals/{ProposalId}/location/{LocationId}Links a specific location to a proposal and triggers tax recalculation.