Portal.io contacts represent the clients and companies in your account. Learn how to create, search, and assign contacts and locations to proposals.
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.
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:
partyType
Description
Person
An individual person. Requires firstName.
Company
A company or organization. Requires companyName.
Contacts also have a contactType that categorizes their relationship to your business:
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.
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.
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.
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.
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'
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.