Account Information
Integrate account requests, consents, authorization, sync, and risk reports.
Goal: Obtain customer consent for read access to their bank data, sync account information, and optionally generate a risk report.
Estimated time: 30 minutes
Prerequisites
- Sandbox credentials (
appId,apiKey, tenantUAE) - An AIS subscription enabled on your Spare account (see Coverage)
Concept first
Read Account Information for the request β consent β authorize β sync model before integrating.
Account Information in the UAE follows a core path: create an account request, create an account consent, complete bank authorization, run a bulk sync, and poll until sync completes. You can then generate a risk report from the synced data. All calls use Authorization: Bearer and x-tenant: UAE.
Authenticate
Exchange your API credentials for a Bearer access token. See Quick Start Setup or the Authentication reference.
curl -X POST https://api.sandbox.tryspare.ae/auth/api-keys/sessions \
-H "x-tenant: UAE" \
-H "app-id: $SPARE_APP_ID" \
-H "x-api-key: $SPARE_API_KEY"const response = await fetch("https://api.sandbox.tryspare.ae/auth/api-keys/sessions", {
method: "POST",
headers: {
"x-tenant": "UAE",
"app-id": process.env.SPARE_APP_ID!,
"x-api-key": process.env.SPARE_API_KEY!,
},
});
const { data } = await response.json();
const accessToken = data.accessToken as string;import os
import requests
response = requests.post(
"https://api.sandbox.tryspare.ae/auth/api-keys/sessions",
headers={
"x-tenant": "UAE",
"app-id": os.environ["SPARE_APP_ID"],
"x-api-key": os.environ["SPARE_API_KEY"],
},
)
response.raise_for_status()
access_token = response.json()["data"]["accessToken"]var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/auth/api-keys/sessions"))
.header("x-tenant", "UAE")
.header("app-id", System.getenv("SPARE_APP_ID"))
.header("x-api-key", System.getenv("SPARE_API_KEY"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post,
"https://api.sandbox.tryspare.ae/auth/api-keys/sessions");
request.Headers.Add("x-tenant", "UAE");
request.Headers.Add("app-id", Environment.GetEnvironmentVariable("SPARE_APP_ID"));
request.Headers.Add("x-api-key", Environment.GetEnvironmentVariable("SPARE_API_KEY"));
var response = await client.SendAsync(request);req, _ := http.NewRequest(http.MethodPost,
"https://api.sandbox.tryspare.ae/auth/api-keys/sessions", nil)
req.Header.Set("x-tenant", "UAE")
req.Header.Set("app-id", os.Getenv("SPARE_APP_ID"))
req.Header.Set("x-api-key", os.Getenv("SPARE_API_KEY"))
resp, err := http.DefaultClient.Do(req)Create an account request
Define the data permissions and consent window for the customer's authorization. Store the returned id as your requestId for the consent step.
consentExpiryDate,successRedirectUrl, andfailureRedirectUrlare optional.- When you omit
consentExpiryDate, Spare sets consent expiry to one week from today's date.
curl -X POST https://api.sandbox.tryspare.ae/data/account-requests \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE" \
-H "Content-Type: application/json" \
-d '{
"consentExpiryDate": "2026-12-31T23:59:59.000Z",
"countryCode": "AE",
"requestPermissions": ["ReadAccountsBasic", "ReadAccountsDetail", "ReadBalances", "ReadTransactionsDetail"],
"successRedirectUrl": "https://example.com/ais/success",
"failureRedirectUrl": "https://example.com/ais/failure"
}'const response = await fetch("https://api.sandbox.tryspare.ae/data/account-requests", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
"Content-Type": "application/json",
},
body: JSON.stringify({
consentExpiryDate: "2026-12-31T23:59:59.000Z",
countryCode: "AE",
requestPermissions: [
"ReadAccountsBasic",
"ReadAccountsDetail",
"ReadBalances",
"ReadTransactionsDetail",
],
successRedirectUrl: "https://example.com/ais/success",
failureRedirectUrl: "https://example.com/ais/failure",
}),
});
const { data: accountRequest } = await response.json();response = requests.post(
"https://api.sandbox.tryspare.ae/data/account-requests",
headers={
"Authorization": f"Bearer {access_token}",
"x-tenant": "UAE",
},
json={
"consentExpiryDate": "2026-12-31T23:59:59.000Z",
"countryCode": "AE",
"requestPermissions": [
"ReadAccountsBasic",
"ReadAccountsDetail",
"ReadBalances",
"ReadTransactionsDetail",
],
"successRedirectUrl": "https://example.com/ais/success",
"failureRedirectUrl": "https://example.com/ais/failure",
},
)
response.raise_for_status()
account_request = response.json()["data"]var body = """
{"consentExpiryDate":"2026-12-31T23:59:59.000Z","countryCode":"AE",
"requestPermissions":["ReadAccountsBasic","ReadAccountsDetail","ReadBalances","ReadTransactionsDetail"],
"successRedirectUrl":"https://example.com/ais/success","failureRedirectUrl":"https://example.com/ais/failure"}
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/data/account-requests"))
.header("Authorization", "Bearer " + accessToken)
.header("x-tenant", "UAE")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();var payload = new {
consentExpiryDate = "2026-12-31T23:59:59.000Z",
countryCode = "AE",
requestPermissions = new[] { "ReadAccountsBasic", "ReadAccountsDetail", "ReadBalances", "ReadTransactionsDetail" },
successRedirectUrl = "https://example.com/ais/success",
failureRedirectUrl = "https://example.com/ais/failure",
};
using var request = new HttpRequestMessage(HttpMethod.Post,
"https://api.sandbox.tryspare.ae/data/account-requests");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");body := strings.NewReader(`{"consentExpiryDate":"2026-12-31T23:59:59.000Z","countryCode":"AE",
"requestPermissions":["ReadAccountsBasic","ReadAccountsDetail","ReadBalances","ReadTransactionsDetail"],
"successRedirectUrl":"https://example.com/ais/success","failureRedirectUrl":"https://example.com/ais/failure"}`)
req, _ := http.NewRequest(http.MethodPost, "https://api.sandbox.tryspare.ae/data/account-requests", body)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)See Account Requests for all fields and response shapes.
Create an account consent
Link the consent to the account request and the customer's bank (providerId). Redirect the customer to the authorization URL returned in the response.
curl -X POST https://api.sandbox.tryspare.ae/consent/account \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE" \
-H "Content-Type: application/json" \
-d '{
"providerId": "00000000-0000-0000-0000-000000000001",
"requestId": "YOUR_ACCOUNT_REQUEST_ID",
"userType": "Retail",
"purpose": "AccountAggregation"
}'const response = await fetch("https://api.sandbox.tryspare.ae/consent/account", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
"Content-Type": "application/json",
},
body: JSON.stringify({
providerId: "00000000-0000-0000-0000-000000000001",
requestId: accountRequest.id,
userType: "Retail",
purpose: "AccountAggregation",
}),
});
const { data: consent } = await response.json();response = requests.post(
"https://api.sandbox.tryspare.ae/consent/account",
headers={
"Authorization": f"Bearer {access_token}",
"x-tenant": "UAE",
},
json={
"providerId": "00000000-0000-0000-0000-000000000001",
"requestId": account_request["id"],
"userType": "Retail",
"purpose": "AccountAggregation",
},
)var body = String.format("""
{"providerId":"00000000-0000-0000-0000-000000000001","requestId":"%s","userType":"Retail","purpose":"AccountAggregation"}
""", accountRequestId);var payload = new {
providerId = "00000000-0000-0000-0000-000000000001",
requestId = accountRequestId,
userType = "Retail",
purpose = "AccountAggregation",
};payload := fmt.Sprintf(`{"providerId":"00000000-0000-0000-0000-000000000001",
"requestId":"%s","userType":"Retail","purpose":"AccountAggregation"}`, accountRequestId)See Account Consents.
Complete authorization
Send the customer to the authorization URL returned on the consent. After they approve access at their bank, Spare completes the bank callback. Poll the consent until it is authorized, then start a sync.
See Authorization and Account Consents.
Sync account data
Start a sync job for the authorized consent. Set the boolean flags for each data slice you need (accounts, balances, transactions, and related objects). The response includes a jobId you use to check progress in the next step.
curl -X POST "https://api.sandbox.tryspare.ae/data/sync?consentId=YOUR_CONSENT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE" \
-H "Content-Type: application/json" \
-d '{
"syncAccounts": true,
"syncBalances": true,
"syncTransactions": true
}'const params = new URLSearchParams({ consentId });
const response = await fetch(`https://api.sandbox.tryspare.ae/data/sync?${params}`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
"Content-Type": "application/json",
},
body: JSON.stringify({
syncAccounts: true,
syncBalances: true,
syncTransactions: true,
}),
});
const { data: { jobId } } = await response.json();response = requests.post(
"https://api.sandbox.tryspare.ae/data/sync",
params={"consentId": consent_id},
headers={"Authorization": f"Bearer {access_token}", "x-tenant": "UAE"},
json={"syncAccounts": True, "syncBalances": True, "syncTransactions": True},
)
job_id = response.json()["data"]["jobId"]var uri = URI.create("https://api.sandbox.tryspare.ae/data/sync?consentId=" + consentId);var url = $"https://api.sandbox.tryspare.ae/data/sync?consentId={consentId}";url := "https://api.sandbox.tryspare.ae/data/sync?consentId=" + consentIdSee Sync.
Poll sync job status (optional)
After you start a sync job, call this endpoint on a short interval until every slice you requested reports complete. Stop polling when all slices succeed or when a slice fails so you can retry or surface an error to the user.
curl -X GET "https://api.sandbox.tryspare.ae/data/sync/YOUR_JOB_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE"const response = await fetch(
`https://api.sandbox.tryspare.ae/data/sync/${jobId}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
},
},
);
const { data: syncStatus } = await response.json();response = requests.get(
f"https://api.sandbox.tryspare.ae/data/sync/{job_id}",
headers={"Authorization": f"Bearer {access_token}", "x-tenant": "UAE"},
)
response.raise_for_status()
sync_status = response.json()["data"]var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/data/sync/" + jobId))
.header("Authorization", "Bearer " + accessToken)
.header("x-tenant", "UAE")
.GET()
.build();using var request = new HttpRequestMessage(
HttpMethod.Get,
$"https://api.sandbox.tryspare.ae/data/sync/{jobId}");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
var response = await client.SendAsync(request);req, _ := http.NewRequest(
http.MethodGet,
"https://api.sandbox.tryspare.ae/data/sync/"+jobId,
nil,
)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
resp, err := http.DefaultClient.Do(req)See Sync.
Generate a risk report
A risk report turns synced account data into a scored assessment you can review in your app or share as a PDF. Use it when you need a structured view of a customer's financial profile after sync completes. The flow has four parts: create the report, wait until it is ready, read the results, and optionally download a PDF copy.
3.1 Create a risk report
Ask Spare to build a report for an authorized consent. You receive a reportId to track progress.
curl -X POST https://api.sandbox.tryspare.ae/report/risk-reports \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE" \
-H "Content-Type: application/json" \
-d '{ "consentId": "YOUR_CONSENT_ID" }'const createResponse = await fetch("https://api.sandbox.tryspare.ae/report/risk-reports", {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
"Content-Type": "application/json",
},
body: JSON.stringify({ consentId }),
});
const { data: reportJob } = await createResponse.json();
const reportId = reportJob.reportId as string;response = requests.post(
"https://api.sandbox.tryspare.ae/report/risk-reports",
headers={"Authorization": f"Bearer {access_token}", "x-tenant": "UAE"},
json={"consentId": consent_id},
)
response.raise_for_status()
report_id = response.json()["data"]["reportId"]var body = String.format("{\"consentId\":\"%s\"}", consentId);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/report/risk-reports"))
.header("Authorization", "Bearer " + accessToken)
.header("x-tenant", "UAE")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();var payload = new { consentId };
using var request = new HttpRequestMessage(HttpMethod.Post,
"https://api.sandbox.tryspare.ae/report/risk-reports");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");body := strings.NewReader(fmt.Sprintf(`{"consentId":"%s"}`, consentId))
req, _ := http.NewRequest(http.MethodPost, "https://api.sandbox.tryspare.ae/report/risk-reports", body)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)3.2 Poll the risk report status
Report generation runs asynchronously. Poll this endpoint until the status shows the report is ready, or until it fails so you can retry.
curl -X GET "https://api.sandbox.tryspare.ae/report/risk-reports/YOUR_REPORT_ID/status" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE"const statusResponse = await fetch(
`https://api.sandbox.tryspare.ae/report/risk-reports/${reportId}/status`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
},
},
);
const { data: reportStatus } = await statusResponse.json();response = requests.get(
f"https://api.sandbox.tryspare.ae/report/risk-reports/{report_id}/status",
headers={"Authorization": f"Bearer {access_token}", "x-tenant": "UAE"},
)
response.raise_for_status()
report_status = response.json()["data"]var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/report/risk-reports/" + reportId + "/status"))
.header("Authorization", "Bearer " + accessToken)
.header("x-tenant", "UAE")
.GET()
.build();using var request = new HttpRequestMessage(
HttpMethod.Get,
$"https://api.sandbox.tryspare.ae/report/risk-reports/{reportId}/status");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
var response = await client.SendAsync(request);req, _ := http.NewRequest(
http.MethodGet,
"https://api.sandbox.tryspare.ae/report/risk-reports/"+reportId+"/status",
nil,
)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
resp, err := http.DefaultClient.Do(req)3.3 Get a risk report
When the report is ready, fetch the full scored result (affordability signals, account summaries, and related fields) as JSON for display in your application.
curl -X GET "https://api.sandbox.tryspare.ae/report/risk-reports/YOUR_REPORT_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE"const reportResponse = await fetch(
`https://api.sandbox.tryspare.ae/report/risk-reports/${reportId}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
},
},
);
const { data: report } = await reportResponse.json();response = requests.get(
f"https://api.sandbox.tryspare.ae/report/risk-reports/{report_id}",
headers={"Authorization": f"Bearer {access_token}", "x-tenant": "UAE"},
)
response.raise_for_status()
report = response.json()["data"]var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/report/risk-reports/" + reportId))
.header("Authorization", "Bearer " + accessToken)
.header("x-tenant", "UAE")
.GET()
.build();using var request = new HttpRequestMessage(
HttpMethod.Get,
$"https://api.sandbox.tryspare.ae/report/risk-reports/{reportId}");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
var response = await client.SendAsync(request);req, _ := http.NewRequest(
http.MethodGet,
"https://api.sandbox.tryspare.ae/report/risk-reports/"+reportId,
nil,
)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
resp, err := http.DefaultClient.Do(req)3.4 Export a risk report to PDF
Download a PDF copy of the report for archival, email, or offline review. The response is a file attachment, not JSON.
curl -X GET "https://api.sandbox.tryspare.ae/report/risk-reports/YOUR_REPORT_ID/export" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant: UAE" \
-o risk-report.pdfconst pdfResponse = await fetch(
`https://api.sandbox.tryspare.ae/report/risk-reports/${reportId}/export`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"x-tenant": "UAE",
},
},
);
const pdfBuffer = await pdfResponse.arrayBuffer();response = requests.get(
f"https://api.sandbox.tryspare.ae/report/risk-reports/{report_id}/export",
headers={"Authorization": f"Bearer {access_token}", "x-tenant": "UAE"},
)
response.raise_for_status()
with open("risk-report.pdf", "wb") as pdf_file:
pdf_file.write(response.content)var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sandbox.tryspare.ae/report/risk-reports/" + reportId + "/export"))
.header("Authorization", "Bearer " + accessToken)
.header("x-tenant", "UAE")
.GET()
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofByteArray());
Files.write(Path.of("risk-report.pdf"), response.body());using var request = new HttpRequestMessage(
HttpMethod.Get,
$"https://api.sandbox.tryspare.ae/report/risk-reports/{reportId}/export");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("x-tenant", "UAE");
var response = await client.SendAsync(request);
await File.WriteAllBytesAsync("risk-report.pdf", await response.Content.ReadAsByteArrayAsync());req, _ := http.NewRequest(
http.MethodGet,
"https://api.sandbox.tryspare.ae/report/risk-reports/"+reportId+"/export",
nil,
)
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("x-tenant", "UAE")
resp, err := http.DefaultClient.Do(req)
// read resp.Body and write to risk-report.pdfSee Risk Reports and the Risk Report guide page.
Key takeaways
- The UAE AIS flow is request β consent β authorize β sync β poll sync status β risk report.
- Every call requires
Authorization: Bearerandx-tenant: UAE. - Account Information endpoints integrate over REST today; official SDK coverage for AIS is planned after the SDK review completes.