The FRAGMENT API is available in the following AWS regions:
us-east-1
us-east-2
us-west-2
eu-west-1
The region for a workspace can be found in the API URL in the settings tab of the dashboard. Use the top-left dropdown in the dashboard to create a new workspace.
Contact us if you don't see your desired AWS region.
FRAGMENT uses OAuth2's client credentials flow to authenticate API clients. The call requesting an access token follows the OAuth2 spec. Use an OAuth2 library that supports the client credentials grant to retrieve the token or make an HTTP request. The flow is:
Authorization
request header with the value Bearer {{access_token}}
when calling the GraphQL API.The token request payload should be in x-www-form-urlencoded
format, with the keys grant_type
, scope
and client_id
.
grant_type=client_credentials&scope={{scope}}&client_id={{client_id}}
The token request headers should contain the following items, where client_credentials
is the Base64-encoded version of: {{client_id}}:{client_secret}}
.
{
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Basic {{client_credentials}}",
"Accept": "*/*"
}
The response is a JSON object containing the access token.
{
"access_token": "<access token>",
"expires_in": 3600,
"token_type": "Bearer"
}
Putting it all together:
import axios from "axios";
const btoa = (str) => Buffer.from(str).toString("base64");
// credentials from the dashboard
const clientId = "2h4t0cv7qv5c9r0qgs1o3erkuk";
const secret = "superSecretClientSecret";
const scope = "https://api.us-west-2.fragment.dev/*";
const authUrl = "https://auth.us-west-2.fragment.dev/oauth2/token";
const getToken = async () => {
// encode the client id and secret
const auth = btoa(`${clientId}:${secret}`);
// create the request body
const data = new URLSearchParams();
data.append("grant_type", "client_credentials");
data.append("scope", scope);
data.append("client_id", clientId);
// retrieve the token
const response = await axios.request({
method: "POST",
url: authUrl,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${auth}`,
Accept: "*/*",
},
data,
});
if (!response.data.access_token) {
throw new Error(
"didn't get an access token from auth endpoint"
);
}
return response.data.access_token;
};
To ensure write operations are executed exactly once, all write mutations in FRAGMENT are idempotent. This enables applications to safely retry operations without risk of duplicate effects. Applications only need to guarantee that the API is called at least once.
Mutations require a unique idempotency key (ik
). For calls to reconcileTx
, syncCustomAccounts
and syncCustomTxs
, FRAGMENT internally uses the transaction or account ID from the request as the idempotency key.
Additional requests with the same ik
are ignored and the original response is returned with isIkReplay: true
in the response.
Idempotency keys are scoped per-Ledger; requests with the same IK
to different Ledgers will execute the mutation and return isIkReplay: false
.
According to the GraphQL spec, all responses will return an object containing data
and errors
.
data
: the result of a query or mutation. In the FRAGMENT API, all successful mutations return a union type that represents an application error or a successful result.
For example, the response type of the addLedgerEntry
mutation is:
union AddLedgerEntryResponse =
AddLedgerEntryResult | BadRequestError | InternalError
Queries can return null
if an error was raised during the request. This is accompanied by a non-empty errors
list.
errors
: a list of errors raised during the request. This typically gets populated when issuing a query if a required argument is missing or an invalid ID is provided.
When calling the API, handle errors in the following order:
null
responses from queries by checking the errors
key. This typically happens when querying an item with an invalid ID.__typename
field. This contains BadRequestError
, InternalError
or the appropriate <Mutation>Result
type.InternalError
with retries and exponential backoff.BadRequestError
by failing the operation.<Mutation>Result
types such as AddLedgerEntryResult
.An example of this written in TypeScript:
import assert from 'assert';
type RequestParams = {
query: string;
variables: Record<string, unknown>;
};
const makeRequest = async ({ query, variables }: RequestParams): Promise<Response> => {
const token = await getToken();
return fetch('Fragment GraphQL URL', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
};
const query = '''
mutation addLedgerEntry($ik: SafeString, $entry: LedgerEntryInput!) {
addLedgerEntry(ik: $ik, entry: $entry) {
__typename
...
}
}
''';
const variables = { ik: 'sample-ik', entry: {...} };
const handleRequest = (req: Response) => {
if (req.status === 429 || req.status >= 500) {
// Rate-limited or intermittent http failures. Retry the request.
return handleRequest(await makeRequest({ query, variables }));
}
if ((req.status >= 400 || req.status < 500) && req.status !== 429) {
// Invalid GraphQL request provided to Fragment. Handle the error.
throw new Error('Invalid GraphQL request');
}
// .json() checks that it was a 200
const response = await req.json();
if (response.data.addLedgerEntry.__typename === 'InternalError') {
// Retry the request in case of internal errors, with backoff.
return handleRequest(await makeRequest({ query, variables }));
}
if (response.data.addLedgerEntry.__typename === 'BadRequestError') {
// Invalid request provided to Fragment. Handle the error.
throw new Error('Invalid API request to Fragment');
}
return response;
};
const response = handleRequest(await makeRequest({ query, variables }));
// Entry successfully posted to Fragment. Handle the response.
assert(response.data.addLedgerEntry.__typename === 'AddLedgerEntryResult');
handlePostedEntry(response.data.addLedgerEntry);
To test how your application handles errors, use the X-Fragment-Return-Error
request header to instruct the API to return erroneous responses.
Set the X-Fragment-Return-Error
header to:
internalError
to instruct the API to return an InternalError
badRequestError
to instruct the API to return a BadRequestError
When requesting an erroneous response, FRAGMENT will skip processing your request and return the error immediately.