Authentication
Server integrations exchange a client ID and secret for access tokens using the OAuth 2.0 client_credentials grant. AI assistants and other interactive MCP hosts use delegated access to connect as a signed-in user.
Create API credentials
Creating credentials requires the owner role or the admin role with Manage developer API permission. Other workspace members can view the credential list.
- Sign in at app.filemark.ca and open Developer.
- Create an API client and select only the scopes your integration needs.
- Copy the client secret, which is shown once.
Store the secret in a server-side secret manager and keep it out of browser code and tools, source control, logs, URLs, and messages.
Rotate a leaked secret from Developer to replace it while keeping the client ID and scopes. Revoking a client permanently disables it and invalidates its tokens on the next request.
Get an access token
- Set
resourcetohttps://api.filemark.cafor REST and MCP access, orhttps://api.filemark.ca/mcpfor MCP access only. The trailing-slash form ofhttps://api.filemark.cais also accepted. - Set
scopeto the full scope names your integration needs, separated by spaces. Includemcpfor an MCP connection. - Choose one authentication method, HTTP Basic or the
client_idandclient_secretform fields, and send each parameter once.
curl --request POST \
--url https://api.filemark.ca/oauth2/token \
--user "$FILEMARK_CLIENT_ID:$FILEMARK_CLIENT_SECRET" \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "resource=https://api.filemark.ca" \
--data-urlencode "scope=https://api.filemark.ca/mcp https://api.filemark.ca/tax:compute"import os
import requests
FILEMARK_CLIENT_ID = os.environ["FILEMARK_CLIENT_ID"]
FILEMARK_CLIENT_SECRET = os.environ["FILEMARK_CLIENT_SECRET"]
response = requests.post(
"https://api.filemark.ca/oauth2/token",
auth=(FILEMARK_CLIENT_ID, FILEMARK_CLIENT_SECRET),
data={
"grant_type": "client_credentials",
"resource": "https://api.filemark.ca",
"scope": "https://api.filemark.ca/mcp https://api.filemark.ca/tax:compute",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_CLIENT_ID = process.env.FILEMARK_CLIENT_ID;
const FILEMARK_CLIENT_SECRET = process.env.FILEMARK_CLIENT_SECRET;
const response = await fetch("https://api.filemark.ca/oauth2/token", {
method: "POST",
headers: {
Authorization: `Basic ${Buffer.from(`${FILEMARK_CLIENT_ID}:${FILEMARK_CLIENT_SECRET}`).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "client_credentials",
resource: "https://api.filemark.ca",
scope: "https://api.filemark.ca/mcp https://api.filemark.ca/tax:compute",
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());{
"access_token": "<access-token>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "https://api.filemark.ca/mcp https://api.filemark.ca/tax:compute"
}Tokens last up to 60 minutes.
Token endpoint errors
A failed exchange returns {"error": "...", "error_description": "..."} with one of these OAuth 2.0 errors.
error | Status | Cause |
|---|---|---|
invalid_request | 400 | The form is malformed, a parameter is missing, duplicated, or unsupported, both authentication methods were sent, or the Content-Type is not application/x-www-form-urlencoded. |
invalid_request | 413 | The form body exceeds 256 KiB. |
invalid_client | 401 | The client ID and secret were rejected, or the client is unknown or not active. |
invalid_scope | 400 | A requested scope is unpublished or has not been granted to the client. |
unsupported_grant_type | 400 | The endpoint does not support the requested grant_type. |
invalid_target | 400 | resource is not https://api.filemark.ca, its trailing-slash form, or https://api.filemark.ca/mcp. |
temporarily_unavailable | 429 or 503 | The token request limit was reached (429) or the token service is unavailable (503). Wait the number of seconds in Retry-After before retrying. |