Authentication
Server integrations hold a client ID and secret and exchange them for short-lived access tokens with the OAuth 2.0 client_credentials grant. Interactive AI hosts connect as a signed-in user through delegated access instead.
Create API credentials
- Sign in at app.filemark.ca and open Developer. Creating credentials needs the owner role, or the admin role with the Manage developer API permission; other members see the list read-only.
- Create an API client and select only the scopes your integration needs.
- Copy the client secret when it is shown. It is never shown again.
Store the secret in a server-side secret manager. Never put it in browser code, browser-based tools like the interactive REST reference, source control, logs, URLs, or support messages.
If a secret is exposed, rotate it from the same page: rotation issues a replacement secret and keeps the client ID and scopes. Revoking a client is immediate and permanent; its tokens are refused on their next request.
Get an access token
resourceis required. Sendhttps://api.filemark.cafor a server integration; the trailing-slash form works too. MCP hosts may send the MCP server URLhttps://api.filemark.ca/mcpinstead, but a token minted for that resource works only on the MCP server.scopeis required and lists the scopes your integration needs, written in full. Requestmcponly if you will connect over MCP.- Authenticate with HTTP Basic or with the
client_idandclient_secretform fields, never both, 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 the OAuth 2.0 error shape, {"error": "...", "error_description": "..."}:
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 not a published scope or is not one this client was granted. |
unsupported_grant_type | 400 | grant_type is not one the endpoint supports. |
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-minting budget is exhausted (429) or the token service is unavailable (503). Wait the Retry-After seconds and retry. |