Artham is a marketplace platform that connects fund managers (Investment Advisors registered with SEBI) with clients through distribution partners. As part of this integration, Artham's servers call your (the broker's) API to fetch client information.
| # | Method | Endpoint | Purpose | |
|---|---|---|---|---|
| 1 | GET | /api/v1/clients/{clientCode} | Fetch client KYC and account details | Required |
| 2 | POST | /api/v1/artham/consent-status | API — receive client Approve / Cancel consent decisions with transaction details | Required |
You provide Artham with your base URL. All endpoint paths below are appended to it.
Production: https://api.yourbroker.com
Sandbox: https://sandbox-api.yourbroker.com
Every request from Artham includes two headers for authentication:
| Header | Description | Example |
|---|---|---|
X-Api-Key |
Static API key that identifies Artham as the caller. | artham_live_k8Fj2mNp... |
X-Signature |
HMAC-SHA256 signature of the request, computed using a shared secret. Proves the request hasn't been tampered with. | a1b2c3d4e5f6... (64 hex chars) |
Artham computes the signature as follows:
message = "{HTTP_METHOD}\n{path}\n{body}"
signature = HMAC-SHA256(key: SHARED_SECRET, message: message)
→ output as lowercase hex string
| Component | Description | Example |
|---|---|---|
HTTP_METHOD | Uppercase HTTP method | GET |
path | Request path (no query string, no host) | /api/v1/clients/AB1234 |
body | Raw JSON request body. Empty string "" for GET requests. | "" (GET has no body) |
"GET\n/api/v1/clients/AB1234\n"
X-Api-Key header value. Reject with 401 if it doesn't match the key Artham shared with you.401 if signatures don't match.See Section 6 for complete verification code in Python, Java, and C#.
All responses must follow this structure:
{
"success": true,
"data": { ... },
"error": null
}
{
"success": false,
"data": null,
"error": {
"code": "CLIENT_NOT_FOUND",
"message": "No client found with code AB9999."
}
}
| Status | When to Use |
|---|---|
200 | Success |
400 | Bad request (invalid params, missing fields) |
401 | Invalid API key or signature |
404 | Client not found |
422 | Business rule violation (insufficient limit, etc.) |
500 | Internal server error |
Use these standard error codes in the error.code field:
| Code | Description |
|---|---|
CLIENT_NOT_FOUND | The given clientCode does not exist in your system |
CLIENT_INACTIVE | Client account is deactivated or suspended |
INSUFFICIENT_LIMIT | Client does not have enough available limit/balance |
INVALID_REQUEST | Missing or malformed request parameters |
INTERNAL_ERROR | Unexpected server error on your side |
This is the primary API. Artham calls this to fetch a client's KYC information, personal details, and bank account details when a client is being onboarded onto the advisory platform.
| Parameter | Type | Description |
|---|---|---|
clientCode | string | The client's trading code at your brokerage |
{
"success": true,
"data": {
"clientCode": "AB1234",
"rmCode": "RM001",
"name": "Rajesh Kumar",
"pan": "ABCDE1234F",
"dateOfBirth": "1990-05-15",
"address1": "123 MG Road",
"address2": "Andheri West",
"address3": "",
"city": "Mumbai",
"state": "Maharashtra",
"pinCode": "400058",
"emailAddress": "rajesh@example.com",
"mobileNumber": "9876543210",
"accountNo": "1234567890",
"ifscCode": "HDFC0001234",
"accountType": "S",
"clientTypeCode": 1,
"isNRI": false,
"isPoliticallyExposedPerson": false
},
"error": null
}
| Field | Type | Required | Description |
|---|---|---|---|
clientCode | string | Yes | Client's unique trading code at your brokerage |
rmCode | string | Yes | Relationship Manager code assigned to this client |
name | string | Yes | Full name as registered in your system |
pan | string | Yes | PAN number (10 characters, e.g. ABCDE1234F) |
dateOfBirth | string | Yes | Date of birth in YYYY-MM-DD format |
address1 | string | No | Address line 1 |
address2 | string | No | Address line 2 |
address3 | string | No | Address line 3 |
city | string | No | City |
state | string | No | State |
pinCode | string | No | PIN code (6 digits) |
emailAddress | string | Yes | Registered email address |
mobileNumber | string | Yes | Registered mobile number (10 digits) |
accountNo | string | Yes | Client's primary bank account number |
ifscCode | string | Yes | IFSC code of the bank branch (11 characters) |
accountType | string | Yes | Bank account type: "S" (Savings), "C" (Current), or "O" (Other) |
clientTypeCode | integer | Yes | Client constitution code (e.g. 1 = Individual, 2 = HUF, etc.) |
isNRI | boolean | Yes | true if the client is a Non-Resident Indian |
isPoliticallyExposedPerson | boolean | Yes | true if the client is a Politically Exposed Person (PEP) |
pan, name, dateOfBirth, and bank account details — are accurate and match your KYC records.
accountType values:
| Value | Description |
|---|---|
"S" | Savings account |
"C" | Current account |
"O" | Other |
clientTypeCode values:
| Value | Description |
|---|---|
1 | Individual |
2 | HUF (Hindu Undivided Family) |
3 | Corporate |
4 | Partnership / LLP |
5 | Trust |
If your system uses different codes, please share your mapping and we will align.
| HTTP | Error Code | When |
|---|---|---|
401 | — | Invalid API key or signature |
404 | CLIENT_NOT_FOUND | No client exists with this clientCode |
422 | CLIENT_INACTIVE | Client account is deactivated or suspended |
When an order is generated for a client, Artham sends the client a consent request on WhatsApp (Approve / Cancel buttons) and email (OTP). The moment the client responds, Artham calls this API on your server — so you know which client approved and which client cancelled, along with the transaction details (scrip, quantity, price, amount).
You provide Artham with the URL where you want to receive these notifications. Authentication uses the same X-Api-Key + X-Signature headers described in Section 2 (signature computed over the raw request body).
{
"eventId": "consent-1042",
"clientCode": "AB1234",
"clientName": "Rajesh Kumar",
"requestType": "REBALANCE",
"referenceId": "REB#210",
"status": "APPROVED",
"respondedVia": "WHATSAPP",
"respondedAt": "2026-07-15 14:32:08",
"orders": [
{
"symbol": "TCS",
"exchange": "NSE",
"transactionType": "SELL",
"quantity": 10,
"price": 3450.50,
"amount": 34505.00
},
{
"symbol": "HDFCBANK",
"exchange": "NSE",
"transactionType": "BUY",
"quantity": 20,
"price": 1720.00,
"amount": 34400.00
}
]
}
| Field | Type | Description |
|---|---|---|
eventId | string | Unique ID of this notification |
clientCode | string | The client's trading code — which client responded |
clientName | string | The client's registered name |
requestType | string | CASH_IN, CASH_OUT_PARTIAL, CASH_OUT_FULL, CASH_OUT_FEES, or REBALANCE |
referenceId | string | Artham's transaction reference (CT#<id> / REB#<id>) — matches the reference tags on placed orders |
status | string | "APPROVED" or "CANCELLED" — the client's decision |
respondedVia | string | "WHATSAPP" or "EMAIL_OTP" |
respondedAt | string | When the client responded, YYYY-MM-DD HH:mm:ss IST |
orders[] | array | Transaction lines the client was asked to approve: symbol, exchange (NSE/BSE), transactionType (BUY/SELL), quantity, price (₹, indicative at consent generation), amount (₹, quantity × price) |
{
"success": true
}
Below are complete examples showing how to verify the X-Signature header on your server. Use the approach that matches your tech stack.
import hmac
import hashlib
def verify_signature(shared_secret, method, path, body, received_signature):
"""
Verify the HMAC-SHA256 signature sent by Artham.
Args:
shared_secret: The secret shared by Artham for your broker
method: HTTP method, e.g. "GET"
path: Request path, e.g. "/api/v1/clients/AB1234"
body: Raw request body as string ("" for GET requests)
received_signature: The X-Signature header value
Returns:
True if signature is valid
"""
message = f"{method}\n{path}\n{body}"
expected = hmac.new(
shared_secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected, received_signature)
# ─── Example: Flask middleware ───────────────────────────────
from flask import request, abort
# The credentials Artham shared with you
ARTHAM_API_KEY = "artham_live_k8Fj2mNp..."
ARTHAM_SHARED_SECRET = "your_shared_secret_here"
@app.before_request
def check_artham_signature():
api_key = request.headers.get('X-Api-Key')
signature = request.headers.get('X-Signature')
if not api_key or not signature:
abort(401, 'Missing authentication headers')
if api_key != ARTHAM_API_KEY:
abort(401, 'Unknown API key')
body = request.get_data(as_text=True) or ""
if not verify_signature(
ARTHAM_SHARED_SECRET,
request.method, request.path, body, signature
):
abort(401, 'Invalid signature')
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class ArthamSignatureVerifier {
/**
* Verify the HMAC-SHA256 signature sent by Artham.
*
* @param sharedSecret The secret shared by Artham for your broker
* @param method HTTP method, e.g. "GET"
* @param path Request path, e.g. "/api/v1/clients/AB1234"
* @param body Raw request body ("" for GET requests)
* @param receivedSig The X-Signature header value
*/
public static boolean verifySignature(
String sharedSecret,
String method,
String path,
String body,
String receivedSig
) throws Exception {
String message = method + "\n" + path + "\n" + (body != null ? body : "");
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(
sharedSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"
);
mac.init(keySpec);
byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
String expected = bytesToHex(hash);
// Constant-time comparison
return MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
receivedSig.getBytes(StandardCharsets.UTF_8)
);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
// ─── Example: Spring Boot filter ────────────────────────────
// @Component
// public class ArthamAuthFilter extends OncePerRequestFilter {
// @Value("${artham.api-key}") private String arthamApiKey;
// @Value("${artham.shared-secret}") private String arthamSecret;
//
// @Override
// protected void doFilterInternal(HttpServletRequest req,
// HttpServletResponse res, FilterChain chain) throws ... {
// String apiKey = req.getHeader("X-Api-Key");
// String signature = req.getHeader("X-Signature");
//
// if (!arthamApiKey.equals(apiKey)) {
// res.sendError(401, "Unknown API key"); return;
// }
//
// String body = new String(req.getInputStream().readAllBytes());
// if (!ArthamSignatureVerifier.verifySignature(
// arthamSecret, req.getMethod(),
// req.getRequestURI(), body, signature)) {
// res.sendError(401, "Invalid signature"); return;
// }
// chain.doFilter(req, res);
// }
// }
using System.Security.Cryptography;
using System.Text;
public static class ArthamSignatureVerifier
{
/// <summary>
/// Verify the HMAC-SHA256 signature sent by Artham.
/// </summary>
/// <param name="sharedSecret">The secret shared by Artham</param>
/// <param name="method">HTTP method, e.g. "GET"</param>
/// <param name="path">Request path, e.g. "/api/v1/clients/AB1234"</param>
/// <param name="body">Raw request body ("" for GET)</param>
/// <param name="receivedSignature">X-Signature header value</param>
public static bool VerifySignature(
string sharedSecret,
string method,
string path,
string body,
string receivedSignature)
{
string message = $"{method}\n{path}\n{body ?? ""}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(sharedSecret));
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
string expected = BitConverter.ToString(hash).Replace("-", "").ToLower();
// Constant-time comparison
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(receivedSignature)
);
}
}
// ─── Example: ASP.NET Core middleware ───────────────────────
// public class ArthamAuthMiddleware {
// private readonly RequestDelegate _next;
// private readonly string _apiKey; // from appsettings.json
// private readonly string _secret; // from appsettings.json
//
// public async Task InvokeAsync(HttpContext context) {
// var apiKey = context.Request.Headers["X-Api-Key"].FirstOrDefault();
// var signature = context.Request.Headers["X-Signature"].FirstOrDefault();
//
// if (apiKey != _apiKey) {
// context.Response.StatusCode = 401; return;
// }
//
// context.Request.EnableBuffering();
// using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
// var body = await reader.ReadToEndAsync();
// context.Request.Body.Position = 0;
//
// if (!ArthamSignatureVerifier.VerifySignature(
// _secret, context.Request.Method,
// context.Request.Path, body, signature)) {
// context.Response.StatusCode = 401; return;
// }
// await _next(context);
// }
// }
https://sandbox-api.yourbroker.com, plus the API URL where you want consent status updates (API 2).| Test Case | Expected Result |
|---|---|
| Valid API key + correct signature | 200 with client data |
| Valid API key + wrong signature | 401 Unauthorized |
| Invalid / unknown API key | 401 Unauthorized |
Missing X-Api-Key or X-Signature headers | 401 Unauthorized |
| Get details for an existing client | 200 with all required fields populated |
| Get details for a non-existent client code | 404 with CLIENT_NOT_FOUND |
| Get details for an inactive / suspended client | 422 with CLIENT_INACTIVE |
| Client taps Approve on WhatsApp | API received with status = "APPROVED", respondedVia = "WHATSAPP", and order lines (qty, price, amount) |
| Client taps Cancel on WhatsApp | API received with status = "CANCELLED", respondedVia = "WHATSAPP" |
| Client approves via email OTP instead | API received with respondedVia = "EMAIL_OTP" |
For integration questions, contact the Artham engineering team: