# GraphQL Primer

What is covered on this page:

- [Core concepts](#core-concepts)
  - [Identifiers](#identifiers)
  - [Pagination](#pagination)
- [3PLs](#3pls)
- [Error Handling & Support](#error-handling--support)
- [Consuming the API with different clients](#consuming-the-api-with-different-clients)

---

## Core concepts

This section covers core concepts used throughout the GraphQL API.

### Identifiers

Resources exposed through the API use opaque **`uuid`** identifiers.

If you have an existing **`legacy_id`** and need the corresponding **`uuid`**, call the `uuid` query with the **`legacy_id`** and the entity name:

```graphql
query { 
    uuid(legacy_id:100579698, entity: Order) { 
        data { 
            legacy_id 
            id
        }
    }
}
```

---

### Pagination

The GraphQL API uses **cursor-based pagination**. When querying **connections**, pass **`first`** or **`last`** to control how many items are returned. This helps optimize queries and reduce credit usage.

Request the **`cursor`** field to identify a **`node`**'s position within a **`connection`**. Use that cursor to fetch the next set of items.

Request the **`pageInfo`** field to determine whether additional pages are available. The **`hasNextPage`** and **`hasPreviousPage`** fields indicate whether you can paginate forward or backward. If both are **`false`**, no more pages are available.

Example: fetching product IDs and SKUs from an account with more than 10,000 products.

Query:

```graphql
query {
  products {
    request_id
    complexity
    data {
      pageInfo {
        hasNextPage
        hasPreviousPage
        startCursor
        endCursor
      }
      edges {
        node {
          id
          account_id
          name
          sku
        }
        cursor
      }
    }
  }
}
```

The response would look something like this:

```json
{
  "data": {
    "products": {
      "request_id": "5d3a155d1ca7d3313fe21331",
      "complexity": 101,
      "data": {
        "pageInfo": {
          "hasNextPage": true,
          "hasPreviousPage": false,
          "startCursor": "YXJyYXljb25uZWN0aW9uOjA=",
          "endCursor": "YXJyYXljb25uZWN0aW9uOjk5"
        },
        "edges": [
          {
            "node": {
              "id": "UHJvZHVjdEluZm86MjYzMDAzNjY2",
              "account_id": "QWNjb3VudDo0Mjg1",
              "name": "Gift Cards $10.00 USD",
              "sku": "13828573298740"
            },
            "cursor": "YXJyYXljb25uZWN0aW9uOjA="
          },
          {
            "node": {
              "id": "UHJvZHVjdEluZm86MjYzMDAzNjY4",
              "account_id": "QWNjb3VudDo0Mjg1",
              "name": "Gift Cards $25.00 USD",
              "sku": "13828573331508"
            },
            "cursor": "YXJyYXljb25uZWN0aW9uOjE="
          }
        ]
      }
    }
  }
}
```

This response contains the first 100 products, not the full result set.

To fetch the next 100 results, use the cursor to indicate where to start.

In this example, **`hasNextPage: true`** and **`endCursor: "YXJyYXljb25uZWN0aW9uOjk5"`**, so the next request would look like this:

```graphql
query {
  products {
    request_id
    complexity
    data(after:"YXJyYXljb25uZWN0aW9uOjk5") {
      pageInfo {
        hasNextPage
        hasPreviousPage
        startCursor
        endCursor
      }
      edges {
        node {
          id
          account_id
          name
          sku
        }
        cursor
      }
    }
  }
}
```

> [!NOTE]
> For a brief but excellent explanation on how Pagination or Edges and Nodes work check [graphql.org](https://graphql.org/learn/pagination/).

---

## 3PLs

When executing **queries**, you might want to fetch only data for a particular customer. You have two options for this:

1. Requesting a token using your customer credentials and use it in any further operations made on behalf of that customer.
2. Use your 3PL user and specify the **`customer_account_id`** parameter on the desired queries, when available.

When executing **mutations** that will modify your customer's data you have two ways to achieve that:

1. Requesting a token using your customer credentials and making the requests with that token
2. Use your 3PL user and specify the customer account you are operating on behalf of by using the **`customer_account_id`** in each mutation's input.

#### 3PL customers

You can always check your customers list by doing:

```graphql
query {
  account {
    data {
      customers {
        edges {
          node {
            id
          }
        }
      }
    }
  }
}
```

or:

```graphql
query {
  me {
    data {
      id
      email
      account {
        customers {
          edges {
            node {
              id
            }
          }
        }
      }
    }
  }
}
```

#### Customer warehouse relationship and lot settings

For 3PL accounts, `account.data.customers` can also return customer settings that are useful when deciding whether and how to operate for a customer at a specific warehouse.

Pass `warehouse_id` when you need warehouse-specific relationship data. Use the public API `Warehouse.id` value, such as the IDs returned by `account.data.warehouses`. When `warehouse_id` is omitted, `warehouse_relationship` returns `null` even if the customer has a relationship with one of your warehouses.

`warehouse_relationship` is scoped to the warehouse/customer relationship and includes:

- `from_name` - the customer display name configured on that warehouse relationship.
- `pause_shipping` - whether shipping is paused for that customer at that warehouse.

`lot_tracking_settings` is the customer's lot tracking configuration. It is not scoped by `warehouse_id`, and it returns `null` when the customer does not have lot tracking settings configured.

```graphql
query ThreePlCustomerSettings($warehouse_id: String!) {
  account {
    request_id
    complexity
    data {
      customers(warehouse_id: $warehouse_id, first: 10) {
        edges {
          node {
            id
            legacy_id
            username
            email
            warehouse_relationship {
              from_name
              pause_shipping
            }
            lot_tracking_settings {
              is_active
              priority
              picking_disabled_period_in_days
              verify_lot_when_packing
              exclude_expired_quantity_from_available
              lots_about_to_expire_email
            }
          }
        }
      }
    }
  }
}
```

Variables:

```json
{
  "warehouse_id": "V2FyZWhvdXNlOjExNzkw"
}
```

Example response:

```json
{
  "data": {
    "account": {
      "request_id": "664fa4c96a9d0b7c9f4d3b21",
      "complexity": 12,
      "data": {
        "customers": {
          "edges": [
            {
              "node": {
                "id": "QWNjb3VudDo0Mjg1",
                "legacy_id": 4285,
                "username": "demo-customer",
                "email": "ops@example.com",
                "warehouse_relationship": {
                  "from_name": "Demo Customer",
                  "pause_shipping": false
                },
                "lot_tracking_settings": {
                  "is_active": true,
                  "priority": "fefo",
                  "picking_disabled_period_in_days": 14,
                  "verify_lot_when_packing": true,
                  "exclude_expired_quantity_from_available": true,
                  "lots_about_to_expire_email": "lots@example.com"
                }
              }
            }
          ]
        }
      }
    }
  }
}
```

---

## Error Handling & Support

When errors occur, they are reported on the *errors* section of the response, where an error code is shown.

#### Error Codes

Although not every error gets a specific code, the most common errors are the following:

| Code | Error                  |
| :--- | :--------------------- |
| 3    | Invalid Argument       |
| 5    | Not Found              |
| 6    | Already exists         |
| 7    | Permission denied      |
| 9    | Failed Precondition    |
| 14   | Unavailable            |
| 20   | FormatError            |
| 21   | Service Not Available  |
| 22   | Unexpected Error       |
| 30   | Throttling Error       |

The [Error Glossary](/troubleshooting/error-glossary/) explains what each code means, the causes behind most failures, and the messages returned by each group of operations.

Always make sure to grab the **`request_id`** from the error's detail. You can share it with our customer support and this will help us univocally identify your request along with its original payload.

There might be the case where there was no error but for some reason, you feel something didn't go as expected. You can still get a **`request_id`** from any operation, just require the field **`request_id`** on every query/mutation and feel free to contact support with it.

For support, email developer@shiphero.com or join our [Community](http://community.shiphero.com)

---

## Consuming the API with different clients

- Clients:
  - [Python](#python)
  - [Javascript](#javascript)

---

### Python

The API can be used from any client that supports GraphQL, here we'll show examples to make some requests in python

#### Using gql

```python
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport
_transport = RequestsHTTPTransport( url='https://public-api.shiphero.com/graphql', use_json=True, )
_transport.headers = { "User-Agent": "Mozilla/5.0 (X11; buntu; " + "Linux x86_64; rv:58.0) Gecko/0100101 Firefox/58.0", "Authorization": "Bearer {}".format(YOUR_TOKEN), "content-type": "application/json", }
client = Client( transport=_transport, fetch_schema_from_transport=True, )
query = gql(""" { products { request_id complexity data(first: 10) { edges { node { id sku } } } } } """)
print(client.execute(query))
```

#### Using sgqlc

Using sgqlc

First, you need to generate the schema:

```shell
python3 -m sgqlc.introspection \
 --exclude-deprecated \
 --exclude-description \
 -H "Authorization: Bearer YOUR_TOKEN" \ 
http://public-api.shiphero.com/graphql \ 
sh_public_api.json
```

Now you can autogenerate the types:

```shell
sgqlc-codegen sh_public_api.json sh_public_api_schema.py
```

Now start making requests:

```python
from sgqlc.operation import Operation 
from sgqlc.endpoint.http import HTTPEndpoint 
from sh_public_api_schema import sh_public_api_schema as schema

endpoint = HTTPEndpoint( 'http://http://public-api.shiphero.com/graphql', 
base_headers={'Authorization': 'Bearer YOUR_TOKEN'} 
)

# Build the query
op = Operation(schema.Query)

# Building the products query
ps = op.products()

# Make sure to request the complexity and request_id
ps.complexity() 
ps.request_id()

# Get the first 10 and define the selections
p_data = ps.data(first=10) 
p_data.edges.node.sku() 
p_data.edges.node.id()

# Executing the call
data = endpoint(op)

# Converting results to entities
for p in (op + data).edges: 
	 print(p.node) # will return a ProductInfo entity
```

---

### Javascript

The API can be used from any client that supports GraphQL, here we'll show examples to make some requests in javascript

#### Using graphql-request

```javascript
import { GraphQLClient } from 'graphql-request'

async function main() {
  const endpoint = 'https://public-api.shiphero.com/graphql'

  const graphQLClient = new GraphQLClient(endpoint, {
    headers: {
      authorization: 'Bearer TOKEN',
    },
  })

  const query = /* GraphQL */ `
{
  products {
    request_id
    complexity
    data(first: 10) {
      edges {
        node {
          id
          sku
        }
      }
    }
  }
}
  `

  const data = await graphQLClient.request(query)
  console.log(JSON.stringify(data, undefined, 2))
}

main().catch(error => console.error(error))
```

