# API Overview

Source: https://fragment.dev/api-reference/ledger-overview

[Documentation index](https://fragment.dev/llms.txt)

<a id="ledger-overview"></a>

<a id="graphql-schema"></a>

## GraphQL schema

The Fragment API is a GraphQL API. The GraphQL schema is hosted at:

```plaintext title="GraphQL schema URL"
https://api.fragment.dev/schema.graphql
```

Provide this schema to a GraphQL codegen tool to generate a typed client in any language. A list of clients is available on the [GraphQL website](https://graphql.org/community/tools-and-libraries/?tags=client).

<a id="regions"></a>

## Regions

The Fragment API is available in the following AWS regions:
- `ap-southeast-2`
- `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](https://dashboard.fragment.dev/go/s/workspace). Use the top-left dropdown in the dashboard to create a new workspace.

[Contact us](https://fragment.dev/contact) if you don't see your desired AWS region.

<a id="authentication"></a>

## Authentication

[Visual: authentication. View on the HTML page.](https://fragment.dev/api-reference/ledger-overview)

Fragment uses OAuth2's [client credentials flow](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) 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:

1. Create an API client in the Fragment [dashboard](https://dashboard.fragment.dev/go/s/api-clients). Each API Client is assigned a role that scopes its permissions: Admin grants full read and write access to the workspace, while Reader grants read-only access.
1. Note the auth endpoint URL and OAuth scope for the API Client. This is specific to the AWS region for a given workspace.
1. Get a fresh access token from the token endpoint. The access token expires in 1 hour. We recommend retrieving a new token for each set of calls needed to be made. Multiple tokens can be generated and use simultaneously.
1. To use the token, add the `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`.

```plaintext
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}}`.

```json
{
    "Content-Type": "application/x-www-form-urlencoded",
    "Authorization": "Basic {{client_credentials}}",
    "Accept": "*/*"
}
```

The response is a JSON object containing the access token.

```json
{
    "access_token": "<access token>",
    "expires_in": 3600,
    "token_type": "Bearer"
}
```

Putting it all together:

```javascript
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;
};
```

<a id="idempotency"></a>

## Idempotency

[Visual: idempotency. View on the HTML page.](https://fragment.dev/api-reference/ledger-overview)

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`.

<a id="errors"></a>

## Errors

According to the [GraphQL spec](https://spec.graphql.org/October2021/#sec-Response-Format), 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:
  
```graphql
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:

1. Handle retryable HTTP errors. These are 429 or 5XX.
1. Handle non-retryable HTTP errors such as other 4XX status codes. This typically happens if the query does not conform to the GraphQL spec, like omitting a required variable or selecting a nonexistent field. This class of error can be avoided by using a generated, typed SDK.
1. Handle `null` responses from queries by checking the `errors` key. This typically happens when querying an item with an invalid ID.
1. Query the `__typename` field. This contains `BadRequestError`, `InternalError` or the appropriate `<Mutation>Result` type.
1. Handle `InternalError` with retries and exponential backoff.
1. Handle `BadRequestError` by failing the operation.
1. Handle `<Mutation>Result` types such as `AddLedgerEntryResult`.

An example of this written in TypeScript:

```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.
