Using Aligni’s GraphQL API
Endpoint
Aligni’s GraphQL endpoint is:
https://ORGANIZATION.aligni.com/api/v3/graphql
You need to replace ORGANIZATION with your Aligni organization’s subdomain.
Authentication
Aligni supports personal API tokens. You need to use a different API token for each Aligni organization.
To create a new API token, visit your account settings page and then “API Tokens”. If you don’t already have an API token, you can create one by clicking on “Create new token”.
To authenticate your requests, pass the API token with the following request header:
Authorization: Token API_TOKEN
Here’s an example cURL call that uses an API token:
curl \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Token oid3vLgynoy_Yl1gZkrgkLEq3J" \
-d '{"query": "{ manufacturers { nodes { name id } } }"}' \
https://demo.aligni.org/api/v3/graphql
Download Aligni GraphQL Schema
In most cases you will not need to download the full GraphQL schema. In development, some tools might need or at least benefit from having the entire schema.
In short, if you don’t know that you need this, you probably don’t.
The easiest way to download the whole Aligni GraphQL schema is to use a command line interface (CLI) tool, such as gql-cli (implemented in Python) or graphql-cli (implemented in JavaScript).
To download the schema with gql-cli, install GQL and execute the following command:
gql-cli https://demo.aligni.org/api/v3/graphql --print-schema > aligni-schema.graphql
Guides
To learn more about using the GraphQL API using Python or Ruby, visit the following guides:
- How To Access Aligni GraphQL API Using Python And GQL
- How To Access Aligni GraphQL API Using Ruby And GraphQL Gem
Pagination
Aligni’s GraphQL API limits the number of items that you can fetch in a single request, to protect against excessive or abusive requests to servers. By default, the API will return 10 items when you request a collection of items. By supplying first
or last
, you can request fewer or more items. The maximum value is 100.
If the total number of items is higher than 100, the response is divided into “pages” of the specified size. These pages can be requested one at a time until you retrieve the whole data set.
Key Concepts
- Connection: Represents a collection of data items, providing information about pagination such as total count and page boundaries.
- Edge: Represents a single item in the connection, containing both the item (node) and a cursor for pagination.
- Node: The actual data item within the edge.
Schema Definition
Here’s an example schema definition for a paginated query:
type Query {
parts(
after: String
before: String
first: Int
last: Int
): PartConnection
}
type PartConnection {
edges: [PartEdge]
nodes: [Part]
pageInfo: PageInfo
}
type PartEdge {
node: Part
cursor: String
}
type Part {
id: ID!
partNumber: String
manufacturerPn: String
...
}
type PageInfo {
endCursor: String
startCursor: String
hasNextPage: Boolean
hasPreviousPage: Boolean
}
Query Parameters
- after: The cursor after which to retrieve the next set of items.
- before: The cursor before which to retrieve the previous set of items.
- first: The number of items to retrieve starting from the
after
cursor. The default value is 10. The maximum value is 100. - last: The number of items to retrieve ending at the
before
cursor. The default value is 10. The maximum value is 100.
Response Structure
- edges: An array of edges, each containing:
- node: The actual data item.
- cursor: A unique identifier for the item.
- nodes: An array of nodes.
- pageInfo: An object containing:
- endCursor: The cursor of the last item in the current page.
- startCursor: The cursor of the first item in the current page.
- hasNextPage: A boolean indicating if there are more items to fetch.
- hasPreviousPage: A boolean indicating if there are previous items to fetch.
Example Query
Fetching the first 10 parts using edges:
{
parts(first: 10) {
edges {
node {
id
partNumber
manufacturerPn
}
cursor
}
pageInfo {
endCursor
startCursor
hasNextPage
hasPreviousPage
}
}
}
An example response:
{
"data": {
"parts": {
"edges": [
… Part "nodes" 1-9 are included in this response…
{
"node": {
"id": "itm_0113ZW52YG3186HQZTZZA87J0Z",
"partNumber": "100012",
"manufacturerPn": "LTST-C190GKT"
},
"cursor": "MTA"
}
],
"pageInfo": {
"endCursor": "MTA",
"startCursor": "MQ",
"hasNextPage": true,
"hasPreviousPage": false
}
}
}
}
Each “edge” in the response has a “node” (part) and a “cursor”. The “pageInfo” response has an “endCursor” value. You can use that value to fetch the next page, using the end cursor as the “after” value:
{
parts(first: 10, after: "MTA") {
edges {
node {
id
partNumber
manufacturerPn
}
cursor
}
pageInfo {
endCursor
startCursor
hasNextPage
hasPreviousPage
}
}
}
Response:
{
"data": {
"parts": {
"edges": [
{
"node": {
"id": "itm_0113ZW52YG77AQ9BQW8DZF5ZB2",
"partNumber": "100013",
"manufacturerPn": "67068-0000"
},
"cursor": "MTE"
},
… Part "nodes" 2-10 are included in this response…
],
"pageInfo": {
"endCursor": "MjA",
"startCursor": "MTE",
"hasNextPage": true,
"hasPreviousPage": true
}
}
}
}
You can continue fetching new pages as long as “hasNextPage” is “true”.
Instead of using “edges”, you can fetch the first 10 parts using “nodes”, which won’t give you the cursor of each part:
{
parts(first: 10) {
nodes {
id
partNumber
manufacturerPn
}
pageInfo {
endCursor
startCursor
hasNextPage
hasPreviousPage
}
}
}
Response:
{
"data": {
"parts": {
"nodes": [
… Part "nodes" 1-9 are included in this response…
{
"id": "itm_0113ZW52YG3186HQZTZZA87J0Z",
"partNumber": "100012",
"manufacturerPn": "LTST-C190GKT"
}
],
"pageInfo": {
"endCursor": "MTA",
"startCursor": "MQ",
"hasNextPage": true,
"hasPreviousPage": false
}
}
}
}
Filtering
Filtering allows retrieving specific subsets of data from a GraphQL API based on defined criteria.
Key Concepts
- Filter Input: Defines the structure of the input object used to specify filtering criteria.
- Filter Operator: Specifies the type of comparison to be applied in the filtering operation (e.g., equals, contains, greater than, less than, etc.).
- Filter Field: Refers to the field on which the filtering operation is applied.
- Filter Argument: The argument used in the query to pass the filter input.
Schema Definition
Here’s an example schema definition for a filtered query:
type Query {
parts(
filters: [FiltersInput!]
): PartConnection
}
type FiltersInput {
field: String!
value: OperatorValueInput!
}
type OperatorValueInput {
gt: OperatorScalar
lt: OperatorScalar
eq: OperatorScalar
gte: OperatorScalar
lte: OperatorScalar
in: [OperatorScalar!]
notIn: [OperatorScalar!]
range: RangeInput
}
type RangeInput {
min: RangeScalar!
max: RangeScalar!
}
Filter Operators
- Greater Than (
gt
): Matches values that are greater than the specified value. - Less Than (
lt
): Matches values that are less than the specified value. - Equals (
eq
): Matches the exact value of the field. - Greater Than or Equal (
gte
): Matches values that are greater than or equal to the specified value. - Less Than or Equal (
lte
): Matches values that are less than or equal to the specified value. - In (
in
): Matches values contained in the specified list. - Not In (
notIn
): Matches values not contained in the specified list. - Range (
range
): Matches values that are in the specified range.
All fields described in the filter need to be matched. That means that the filter merges all the conditions using a logical and operator.
Query Example
Here’s an example query that demonstrates how to use filtering:
query {
parts(filters: [
{field: "value", value: {gt: "50"}},
{field: "value", value: {lt: "1000"}}
]) {
nodes {
manufacturerPn
value
}
}
}
Example Response
{
"data": {
"parts": {
"nodes": [
{
"manufacturerPn": "9C06031A1000JLHFT",
"value": 100
},
{
"manufacturerPn": "9C06031A3300JLHFT",
"value": 330
},
{
"manufacturerPn": "RC0603JR-07390RL",
"value": 390
}
]
}
}
}
Error Handling
In GraphQL, HTTP status codes work differently from REST API status codes. The most important difference is that the GraphQL API can return a “200 OK” response code in cases that would typically produce 4xx or 5xx errors in REST.
For that reason, it’s very important to query and check the “errors” part of each GraphQL response.
Errors in Queries
For example, if a user that doesn’t have the permission to see contacts tries to access contacts using the GraphQL API with the following query:
query Contacts {
contacts {
nodes {
email
}
}
}
They will see the following response:
{
"data": null,
"errors": [
{
"message": "You do not have permission to perform the selected action.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"contacts"
]
}
]
}
Note that “data” is “null” and there is “errors” array that includes all errors (in this case, there’s just one).
Errors in Mutations
When you execute a mutation, you need to define what the response will include. It’s advisable to always include “errors” in the response.
For example, the following mutation will create a contact, it will return the ID of the new contact if the request was successful and errors if it wasn’t:
mutation CreateContact {
contactCreate(contactInput: {}) {
contact {
id
}
errors
}
}
Always check if there are any errors in the response.
Rate Limiting
Rate limiting is a technique used to control the amount of incoming and outgoing traffic to and from a server. Nearly all API implementations employ rate limiting to help achieve several goals:
- Improve security by mitigating denial-of-service (DoS) attacks and reducing the risk of brute-force attacks.
- Ensure fair usage of limited server resources among all users.
- Improve the users experience for all users by ensuring that the API remains responsive and available.
Complexity
Complexity is a measure of the computational cost of processing an API query or mutation. Aligni calculates complexity by assigning a score to each field and operation to determine the overall demand of a query. Each requested field in a query is valued at 1.
Additionally, certain segments of the API carry a multiplier that increases the cost of the query based on which area of operation the API is related to.
Note that complexity is subject to change without notice.
API Segment | Query | Mutation |
---|---|---|
Default | 1 | 10 |
Item Master | 1 | 10 |
Inventory | 1 | 10 |
ActiveQuote | 2 | 20 |
Purchasing | 2 | 20 |
Build System | 5 | 100 |
Demand System | 5 | 100 |
Rate Limits
Rate limits establish a maximum velocity at which complexity may be accrued by all API clients within an organization. If a rate limit is exceeded, future requests will return a rate limit error response until the end of the measurement interval when the meter resets.
Complexity is computed as a measure of the database and server impact of a particular query or mutation. The limit and method for measuring this are subject to change without notice. The complexity of each query is returned with the results.
Standard | API Premium | |
---|---|---|
Requests per Minute | 10 | 30 |
Complexity Limit (per request) | 10,000 | 20,000 |
Complexity Limit (per hour) | 600,000 | 3,600,000 |
Maximum Pagination | 100 | 100 |
Maximum Concurrent Requests | 1 | 2 |
The server performs a complexity estimate prior to actually performing the work of an API call. This estimate is based on the maximum expected complexity and could be much greater than the actual complexity in some cases.
Reducing Estimated Complexity
If the server does not know how much data will be returned, it uses the default page size to help with this computation. Therefore, in deeply-nested queries, the estimated complexity can quickly grow. You can mitigate this possibility (and, correspondingly, the possibility of a request being declined based on this estimate) by reducing the depth of queries or by explicitly specifying how many records you want.
The following query, for example, will assume the default page size of 10 to estimate the number of revisions in the query:
revisions(filters: {field: "active", value: {eq: true}})
The following query, on the other hand, specifies that only the first item will be returned, so the estimated complexity will be much smaller:
revisions(first: 1, filters: {field: "active", value: {eq: true}})
Another common example is related to part custom parameters. You can fetch the number of custom parameter fields using the following two queries (for non-revisioned and revisioned parameters, respectively:
partParameterFields(filters: {field: "revisioned", value: {eq: "false"}}) {
totalCountNonRevisioned
}
partParameterFields(filters: {field: "revisioned", value: {eq: "true"}}) {
totalCountRevisioned
}
Then, in a subsequent request for parts, you can refer to these counts to eliminate the ambiguity and make the complexity more definitive. (Note that the variable substitution will need to be performed into the query as appropriate.)
parts {
nodes {
manufacturerPn
customParameters(first: #{totalCountNonRevisioned}) {
nodes {
name
value
}
}
revisions(first: 1, filters: {field: "active", value: {eq: "true"}}) {
nodes {
revisionName
customParameters(first: #{totalCountRevisioned}) {
nodes {
name
value
}
}
}
}
}
}