Open Api > API usage as a third-party application instead of a user
Use the Client Credentials Flow
The OAuth 2.0 Client Credentials flow is designed for machine-to-machine authentication, typically used by third-party applications or backend services that need to interact with the Tallyfy API independently or on behalf of users within an organization.
This method requires you to first obtain a Client ID
and Client Secret
from Tallyfy Support.
This integration pattern is ideal for organizations that want to:
- Embed Tallyfy functionality within their own software.
- Provide integrated workflow capabilities to their users without them needing separate Tallyfy logins visible in your app.
- Automating process management or user provisioning programmatically.
- System-level integrations (e.g., reporting, data synchronization).
- Handle user provisioning programmatically
- Contact Tallyfy Support to request client credentials (
Client ID
andClient Secret
). - Explain your integration needs.
- Tallyfy will provide the credentials for your organization. Store them securely.
Your application first needs its own access token to perform actions like user provisioning or getting user-specific tokens.
- Endpoint:
POST /oauth/token
- Base URL:
https://go.tallyfy.com/api
(The primary API base URL). - OAuth Endpoint:
https://go.tallyfy.com/api/oauth/token
(Used specifically for getting the access token). - Request Body (form-data):
grant_type
:client_credentials
client_id
: Your Application’s Client IDclient_secret
: Your Application’s Client Secretscope
:*
(or specific scopes if provided by Tallyfy)
const fetch = require('node-fetch'); // If in Node.js environmentconst FormData = require('form-data'); // If in Node.js environment
const clientId = 'YOUR_CLIENT_ID';const clientSecret = 'YOUR_CLIENT_SECRET';const tokenUrl = 'https://go.tallyfy.com/api/oauth/token';
const formData = new FormData();formData.append('grant_type', 'client_credentials');formData.append('client_id', clientId);formData.append('client_secret', clientSecret);formData.append('scope', '*'); // Optional, use specific scopes if needed
fetch(tokenUrl, { method: 'POST', body: formData, // Headers for FormData are usually set automatically by fetch/browser // but ensure Content-Type is 'application/x-www-form-urlencoded' if manually setting}).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json();}).then(data => { console.log('Success:', data); // Use data.access_token for subsequent API calls}).catch(error => { console.error('Error fetching token:', error);});
import requests
client_id = 'YOUR_CLIENT_ID'client_secret = 'YOUR_CLIENT_SECRET'token_url = 'https://go.tallyfy.com/api/oauth/token'
payload = { 'grant_type': 'client_credentials', 'client_id': client_id, 'client_secret': client_secret, 'scope': '*' # Optional}
# Use 'data' for application/x-www-form-urlencodedresponse = requests.post(token_url, data=payload)
if response.status_code == 200: token_data = response.json() print("Success:", token_data) # Use token_data['access_token']else: print(f"Error: {response.status_code}") print(response.text)
import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.URLEncoder;import java.nio.charset.StandardCharsets;import java.util.HashMap;import java.util.Map;import java.util.stream.Collectors;
public class TallyfyClientCredentials {
public static void main(String[] args) throws Exception { String clientId = "YOUR_CLIENT_ID"; String clientSecret = "YOUR_CLIENT_SECRET"; String tokenUrl = "https://go.tallyfy.com/api/oauth/token";
Map<String, String> formData = new HashMap<>(); formData.put("grant_type", "client_credentials"); formData.put("client_id", clientId); formData.put("client_secret", clientSecret); formData.put("scope", "*"); // Optional
String form = formData.entrySet() .stream() .map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8)) .collect(Collectors.joining("&"));
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(tokenUrl)) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(form)) .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) { System.out.println("Success:"); System.out.println(response.body()); // Parse JSON response to get access_token } else { System.err.println("Error fetching token: " + response.statusCode()); System.err.println(response.body()); } }}
package main
import ( "fmt" "net/http" "net/url" "strings" "io/ioutil")
func main() { clientId := "YOUR_CLIENT_ID" clientSecret := "YOUR_CLIENT_SECRET" tokenUrl := "https://go.tallyfy.com/api/oauth/token"
data := url.Values{} data.Set("grant_type", "client_credentials") data.Set("client_id", clientId) data.Set("client_secret", clientSecret) data.Set("scope", "*") // Optional
client := &http.Client{} req, err := http.NewRequest("POST", tokenUrl, strings.NewReader(data.Encode())) if err != nil { fmt.Println("Error creating request:", err) return } req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req) if err != nil { fmt.Println("Error sending request:", err) return } defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response body:", err) return } bodyString := string(bodyBytes)
if resp.StatusCode == http.StatusOK { fmt.Println("Success:") fmt.Println(bodyString) // Parse JSON response to get access_token } else { fmt.Printf("Error: %d\n", resp.StatusCode) fmt.Println(bodyString) }}
#include <iostream>#include <string>#include <vector>#include <cpprest/http_client.h>#include <cpprest/filestream.h>
using namespace web;using namespace web::http;using namespace web::http::client;
pplx::task<void> GetClientCredentialsToken(){ http_client client(U("https://go.tallyfy.com/api/oauth/token"));
uri_builder builder; builder.append_query(U("grant_type"), U("client_credentials")); builder.append_query(U("client_id"), U("YOUR_CLIENT_ID")); builder.append_query(U("client_secret"), U("YOUR_CLIENT_SECRET")); builder.append_query(U("scope"), U("*")); // Optional
http_request request(methods::POST); request.headers().set_content_type(U("application/x-www-form-urlencoded")); request.set_body(builder.query());
return client.request(request).then([](http_response response) { if (response.status_code() == status_codes::OK) { return response.extract_json(); } else { // Handle error - extracting body might be useful return response.extract_string().then([](utility::string_t body) { std::wcerr << L"Error response body: " << body << std::endl; // Throw or return an error indicator wrapped in json::value for consistency if needed return pplx::task_from_result(json::value::null()); }); } }).then([](pplx::task<json::value> previousTask) { try { json::value const & v = previousTask.get(); if (!v.is_null()) { std::wcout << L"Success:\n" << v.serialize() << std::endl; // Use v.at(U("access_token")).as_string() } else { std::wcerr << L"Error fetching token (check previous logs)." << std::endl; } } catch (const http_exception& e) { std::wcerr << L"HTTP Exception caught: " << e.what() << std::endl; } catch (const std::exception& e) { std::wcerr << L"Exception caught: " << e.what() << std::endl; } catch (...) { std::wcerr << L"Unknown exception caught" << std::endl; } });}
int main(){ try { GetClientCredentialsToken().wait(); } catch (const std::exception &e) { std::wcerr << L"Error in main: " << e.what() << std::endl; } catch (...) { std::wcerr << L"Unknown exception caught in main" << std::endl; }
return 0;}// Note: Requires C++ REST SDK (Casablanca). Ensure proper setup and linking.// This is a basic example; robust applications need better error handling,// JSON parsing, and potentially asynchronous management.
using System;using System.Collections.Generic;using System.Net.Http;using System.Threading.Tasks;
public class TallyfyAuth{ private static readonly HttpClient client = new HttpClient();
public static async Task GetClientCredentialsToken() { var clientId = "YOUR_CLIENT_ID"; var clientSecret = "YOUR_CLIENT_SECRET"; var tokenUrl = "https://go.tallyfy.com/api/oauth/token";
var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("grant_type", "client_credentials"), new KeyValuePair<string, string>("client_id", clientId), new KeyValuePair<string, string>("client_secret", clientSecret), new KeyValuePair<string, string>("scope", "*") // Optional });
try { HttpResponseMessage response = await client.PostAsync(tokenUrl, formContent); string responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) { Console.WriteLine("Success:"); Console.WriteLine(responseBody); // TODO: Deserialize JSON (e.g., using System.Text.Json or Newtonsoft.Json) // var tokenData = JsonSerializer.Deserialize<TokenResponse>(responseBody); // Console.WriteLine($"Access Token: {tokenData.AccessToken}"); } else { Console.WriteLine($"Error: {response.StatusCode}"); Console.WriteLine(responseBody); } } catch (HttpRequestException e) { Console.WriteLine($"Request Exception: {e.Message}"); } }
// Example usage (e.g., in a Main method) // static async Task Main(string[] args) // { // await GetClientCredentialsToken(); // }
// Define a class to deserialize the JSON response if needed // public class TokenResponse // { // [JsonPropertyName("access_token")] // public string AccessToken { get; set; } // [JsonPropertyName("token_type")] // public string TokenType { get; set; } // [JsonPropertyName("expires_in")] // public int ExpiresIn { get; set; } // }}
Response:
{ "token_type": "Bearer", "expires_in": 3600, // Typically 1 hour "access_token": "APP_LEVEL_ACCESS_TOKEN_EXAMPLE..."}
Using the application-level token obtained in Step 2, you can manage users. Refer to the Members code samples section for specific examples once available. The general endpoint mentioned previously was POST /api/applications/{orgID}/users
, but check the current Swagger definition for the correct endpoint, likely under /organizations/{org}/users/invite
or similar, using the application token as the Bearer token.
To act as a specific user, request a user-specific token using the application token.
- Endpoint:
POST /api/applications/{orgID}/users/{email}/token
(Verify this endpoint with Tallyfy Support or Swagger spec; it might differ from standard OAuth patterns). - Base URL: Check with Tallyfy Support if this specific endpoint uses
go.tallyfy.com
orapi.tallyfy.com
. - Headers:
Authorization: Bearer {your_app_access_token}
(From Step 2)Content-Type: application/json
X-Tallyfy-Client: APIClient
Response:
{ "token_type": "Bearer", "expires_in": 3600, // User token lifetime "access_token": "USER_SPECIFIC_ACCESS_TOKEN_EXAMPLE..."}
Use the appropriate token (APP_LEVEL_ACCESS_TOKEN
or USER_SPECIFIC_ACCESS_TOKEN
) in the Authorization: Bearer {token}
header for subsequent API calls, along with the standard Accept
and X-Tallyfy-Client
headers.
Example: Get tasks for a specific user (using user-specific token)
- Endpoint:
GET /organizations/{orgID}/me/tasks
- Base URL:
https://go.tallyfy.com/api
- Resource Path: e.g.,
/organizations/{org_id}/users
- Full URL:
https://go.tallyfy.com/api/organizations/{org_id}/users
- Headers:
Authorization: Bearer {user_specific_access_token}
Accept: application/json
X-Tallyfy-Client: APIClient
Code examples for making the requests themselves are similar to those shown in the Personal Access Token guide, just substitute the correct token.
- Store client credentials securely (e.g., encrypted secrets management).
- Protect both application-level and user-specific tokens.
- Rotate secrets periodically.
- Use HTTPS for all communications.
- Implement robust token expiration and refresh logic.
Open Api > OAuth authorization flow for third-party applications
Open Api > Integrate with Tallyfy using the API
- 2025 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks