Every Portal.io API request must include anDocumentation Index
Fetch the complete documentation index at: https://docs.portal.io/llms.txt
Use this file to discover all available pages before exploring further.
X-MSS-SIGNATURE header containing a Base64-encoded HMAC-SHA256 signature. The signature is computed from a canonical message you build from properties of the request itself, then signed using your Secret Key. This page explains exactly how to construct the canonical message and compute the signature.
Canonical message format
The canonical message is a single string formed by concatenating these components with no separator: For GET requests:- HTTP method — uppercase, e.g.
GET,POST,PUT - Base URL — the scheme, host, and path only. Do not include query parameters. For example, use
https://api.portal.io/public/proposalseven if the actual request URL has?PageNumber=1&PageSize=10appended. - Content type — the exact value of the
Content-Typeheader. Include this segment only for non-GET requests. Most Portal.io POST endpoints useapplication/x-www-form-urlencoded. The AI Builder endpoints (generate-outlineandbuild-proposal) useapplication/json. Always check the endpoint’s documentation for the correct value — the content type in your signing string must match theContent-Typeheader exactly or the request will fail with 401. - Timestamp — the exact value you send in
X-MSS-CUSTOM-DATE - User API key — the exact value you send in
X-MSS-API-USERKEY
Rules
- Use the base URL without query parameters. This is by design — query parameters are sent in the request as normal, but the server intentionally excludes them from signature verification. Only the scheme, host, and path are signed.
- For
GETrequests, omit the content-type segment completely. Do not include an empty string in its place. - For non-
GETrequests, include the exactContent-Typevalue from the request header. The value in the signing string and the value in the header must match exactly — including case and any suffixes (e.g.application/x-www-form-urlencoded, notApplication/X-WWW-Form-Urlencoded). - The request body is not part of the canonical message. Only the content type is included, not the body itself.
- The timestamp must exactly match the value in
X-MSS-CUSTOM-DATE, character for character. - The user API key must exactly match the value in
X-MSS-API-USERKEY, character for character. - For the initial credential exchange, the user API key is an empty string in both the header and the canonical message.
Examples
GET request (credential exchange)
For the initial credential exchange, where the user API key is empty, the canonical message looks like this:- Method:
GET - Base URL:
https://api.portal.io/authenticate/apikeyexchange - Content type: (omitted — this is a GET request)
- Timestamp:
Mon, 06 Apr 2026 00:22:19 GMT - User API key: (empty string — this is the initial exchange)
?UserName=...&Password=...), but the canonical message uses only the base URL without them.
GET request (with query parameters)
When listing proposals with pagination, the canonical message is:?PageNumber=1&PageSize=10, but those query parameters are not in the signed string.
POST request (adding an area to a proposal)
For a POST request, the content type is included between the URL and the timestamp:- Method:
POST - Base URL:
https://api.portal.io/public/proposals/1042/area - Content type:
application/x-www-form-urlencoded - Timestamp:
Mon, 06 Apr 2026 00:22:19 GMT - User API key:
qBOSOYDeZaSzTxqMCL1Kr66JpU2H6wHCLz7xviZUOcA=
Name=Living+Room) is sent normally but is not part of the canonical message.
Computing the signature
Once you have the canonical message, compute the HMAC-SHA256 using your Secret Key as raw ASCII bytes, then Base64-encode the raw digest.Full request example
Here is how the computed signature fits into a complete request. This example performs the initial credential exchange:BASE64_HMAC_SIGNATURE with the output of your signing function. Replace YOUR_APP_ID with your API Application Key. The X-MSS-API-USERKEY header is intentionally empty for this call.
Once you have your User API Key, include it in both X-MSS-API-USERKEY and your canonical message on all subsequent requests.