> Documentation index: [Saleor](/llms.txt) · [This section](/quickstart/llms.txt)
> Source: https://docs.saleor.io/quickstart/api

# API Walkthrough

Getting familiar with basic APIs and Webhooks. This guide assumes basic knowledge of GraphQL.

Communicating with Saleor is done via public and private GraphQL APIs and webhooks:

-   **Public APIs**: Used for storefront clients, mobile apps, and other public-facing applications that do not store secret tokens.
-   **Admin APIs**: Can be accessed by backend services, [admin dashboards](https://github.com/saleor/saleor-dashboard), apps or backends for front-ends.
-   **Webhooks:** Used to notify external systems about events in Saleor, such as order creation, order payment, or order fulfillment.
-   **Sync Webhooks:** Allow to modify saleor responses, for when user adds item to cart, webhook would call your backend to for custom price calculation.

GraphQL provides [many benefits](/overview/why-saleor/graphql.md) and one of them is [tooling](/api-usage/developer-tools.md) that eliminates the needs for dedicated SDKs. To keep this guide universal to any technology stack, we will use GraphQL playground, but you can paste the queries to your preferred language or tool.

1

<a id="use-sample-dataset"></a>

### Use sample dataset

To make this guide easier to follow, we recommend using a sample dataset to ensure that the IDs used in example queries and mutations are identical.

For Saleor Cloud: Go to instance settings and choose _"Replace database with a sample"_.

For local development follow this [step](/quickstart/running-locally.md#add-sample-data).

2

<a id="open-graphql-playground"></a>

### Open GraphQL playground

Saleor comes with GraphQL pre-installed, which can be accessed in two ways:

**Saleor Dashboard:** The dashboard is available at use `cmd+'` to open the playground. All queries will be on behalf of the authenticated user, therefore the queries will be limited to the user's permissions.

**Public playground:** Each instance has `/graphql/` endpoint available for public access. For example `https://{your_store}.saleor.cloud/graphql/` or `http://localhost:8000/graphql/` All queries will be via public access unless app token headers are specified.

For this tutorial, we will use the public playground to execute queries. But you can use the dashboard playground as well, as it will not require additional credentials (assuming you are the admin).

3

<a id="fetch-products"></a>

### Fetch products

Retrieve a list of 10 products with a minimal price between 10 and 100.

In the result tab, we get a [paginated list](/api-usage/pagination.md) of products with their names and categories.

Each query also returns the [cost of the query](/api-usage/usage-limits.md).

**GraphQL**

```graphql
{
  products(
    first: 10
    channel: "default-channel"
    where: { minimalPrice: { range: { gte: 10, lte: 100 } } }
  ) {
    edges {
      node {
        id
        name
        category {
          id
          name
        }
      }
    }
  }
}
```

**Result**

```json
{
  "data": {
    "products": {
      "edges": [
        {
          "node": {
            "id": "UHJvZHVjdDoxMzQ=",
            "name": "Monospace Tee",
            "category": {
              "id": "Q2F0ZWdvcnk6Mzk=",
              "name": "T-shirts"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMzA=",
            "name": "Paul's Balance 420",
            "category": {
              "id": "Q2F0ZWdvcnk6Mjg=",
              "name": "Sneakers"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxNDU=",
            "name": "Battle-tested at brands like Lush",
            "category": {
              "id": "Q2F0ZWdvcnk6MjY=",
              "name": "Audiobooks"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMzI=",
            "name": "Blue Hoodie",
            "category": {
              "id": "Q2F0ZWdvcnk6Mjk=",
              "name": "Sweatshirts"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMjg=",
            "name": "Blue Plimsolls",
            "category": {
              "id": "Q2F0ZWdvcnk6Mjg=",
              "name": "Sneakers"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMzc=",
            "name": "Blue Polygon Shirt",
            "category": {
              "id": "Q2F0ZWdvcnk6Mzk=",
              "name": "T-shirts"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxNjE=",
            "name": "Cubes Fountain Tee",
            "category": {
              "id": "Q2F0ZWdvcnk6Mzk=",
              "name": "T-shirts"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMzY=",
            "name": "Darko Polo",
            "category": {
              "id": "Q2F0ZWdvcnk6NDA=",
              "name": "Polo shirts"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMzg=",
            "name": "Dark Polygon Tee",
            "category": {
              "id": "Q2F0ZWdvcnk6Mzk=",
              "name": "T-shirts"
            }
          }
        },
        {
          "node": {
            "id": "UHJvZHVjdDoxMjk=",
            "name": "Dash Force",
            "category": {
              "id": "Q2F0ZWdvcnk6Mjg=",
              "name": "Sneakers"
            }
          }
        }
      ]
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 20,
      "maximumAvailable": 50000
    }
  }
}
```

4

<a id="get-specific-product"></a>

### Get specific product

Retrieve the first product ID from the previous query and fetch the product details.

It is possible to fetch many fields associated with products. To conveniently explore the fields that can be added to the query, use the auto-complete by using `cmd+space` or `ctrl+space` on Windows. Or use the folder icon in the left side panel to explore the fields and interactively add them to the query.

Note that some fields are private and require specific permissions, which we will cover in the later steps.

**GraphQL**

```graphql
{
  product(id: "UHJvZHVjdDoxMzQ=", channel: "default-channel") {
    id
    seoTitle
    seoDescription
    availableForPurchaseAt
    created
    externalReference
    isAvailable(address: { country: US })
    media(sortBy: { direction: ASC, field: ID }) {
      id
      alt
    }
    name
    variants {
      created
      id
      media {
        alt
      }
    }
  }
}
```

**Result**

```json
{
  "data": {
    "product": {
      "id": "UHJvZHVjdDoxMzQ=",
      "seoTitle": "",
      "seoDescription": "",
      "availableForPurchaseAt": "2022-05-13T21:22:42.298000+00:00",
      "created": "2023-12-29T09:47:45.467273+00:00",
      "externalReference": null,
      "isAvailable": true,
      "media": [
        {
          "id": "UHJvZHVjdE1lZGlhOjE3",
          "alt": ""
        },
        {
          "id": "UHJvZHVjdE1lZGlhOjE4",
          "alt": ""
        }
      ],
      "name": "Monospace Tee",
      "variants": [
        {
          "created": "2023-12-29T09:47:45.931645+00:00",
          "id": "UHJvZHVjdFZhcmlhbnQ6MzQ4",
          "media": [
            {
              "alt": ""
            }
          ]
        },
        {
          "created": "2023-12-29T09:47:45.942318+00:00",
          "id": "UHJvZHVjdFZhcmlhbnQ6MzQ5",
          "media": [
            {
              "alt": ""
            }
          ]
        },
        {
          "created": "2023-12-29T09:47:45.952070+00:00",
          "id": "UHJvZHVjdFZhcmlhbnQ6MzUw",
          "media": [
            {
              "alt": ""
            }
          ]
        },
        {
          "created": "2023-12-29T09:47:45.961975+00:00",
          "id": "UHJvZHVjdFZhcmlhbnQ6MzUx",
          "media": [
            {
              "alt": ""
            }
          ]
        },
        {
          "created": "2023-12-29T09:47:45.971912+00:00",
          "id": "UHJvZHVjdFZhcmlhbnQ6MzUy",
          "media": [
            {
              "alt": ""
            }
          ]
        }
      ]
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 4,
      "maximumAvailable": 50000
    }
  }
}
```

5

<a id="create-a-checkout"></a>

### Create a checkout

Let's create a checkout with a product variant we fetched in the previous step.

Checkout only accepts the variant ID, not the product, as variants have [inventory and pricing information](/developer/products/overview.md#product-and-variants).

**GraphQL**

```graphql
mutation {
  checkoutCreate(
    input: {
      channel: "default-channel"
      lines: { quantity: 1, variantId: "UHJvZHVjdFZhcmlhbnQ6MzQ4" }
    }
  ) {
    checkout {
      id
      lines {
        id
        variant {
          name
        }
      }
    }
    errors {
      field
      message
    }
  }
}
```

**Result**

```json
{
  "data": {
    "checkoutCreate": {
      "checkout": {
        "id": "Q2hlY2tvdXQ6NmUzMzRlMjMtOWJiNS00MWUwLTk1NzYtZDc0NmJmMzQ4NDc1",
        "lines": [
          {
            "id": "Q2hlY2tvdXRMaW5lOjEwMGFkMTUwLWNhZTktNDVlMi1hZDIxLTE3MDVmZDAxZWM3NQ==",
            "variant": {
              "name": "S"
            }
          }
        ]
      },
      "errors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 1,
      "maximumAvailable": 50000
    }
  }
}
```

6

<a id="update-checkout"></a>

### Update checkout

Let's add the user and billing addresses to the checkout and assign them to an anonymous user.

Notice that we can add two mutations in a single request. In the last mutation, we updated the email address of the checkout and requested the shipping methods available for the checkout.

**GraphQL**

```graphql
mutation {
  checkoutShippingAddressUpdate(
    checkoutId: "Q2hlY2tvdXQ6NmUzMzRlMjMtOWJiNS00MWUwLTk1NzYtZDc0NmJmMzQ4NDc1"
    shippingAddress: {
      firstName: "First"
      lastName: "Last"
      streetAddress1: "8066 Madison St."
      city: "Brooklyn"
      postalCode: "11237"
      country: US
      countryArea: "NY"
    }
  ) {
    errors {
      field
      message
    }
  }
  checkoutBillingAddressUpdate(
    checkoutId: "Q2hlY2tvdXQ6NmUzMzRlMjMtOWJiNS00MWUwLTk1NzYtZDc0NmJmMzQ4NDc1"
    billingAddress: {
      firstName: "First"
      lastName: "Last"
      streetAddress1: "8066 Madison St."
      city: "Brooklyn"
      postalCode: "11237"
      country: US
      countryArea: "NY"
    }
  ) {
    errors {
      field
      message
    }
  }
  checkoutEmailUpdate(
    checkoutId: "Q2hlY2tvdXQ6NmUzMzRlMjMtOWJiNS00MWUwLTk1NzYtZDc0NmJmMzQ4NDc1"
    email: "test@test.com"
  ) {
    checkout {
      shippingMethods {
        id
        name
      }
    }
    errors {
      field
      message
    }
  }
}
```

**Result**

```json
{
  "data": {
    "checkoutShippingAddressUpdate": {
      "errors": []
    },
    "checkoutBillingAddressUpdate": {
      "errors": []
    },
    "checkoutEmailUpdate": {
      "checkout": {
        "shippingMethods": [
          {
            "id": "U2hpcHBpbmdNZXRob2Q6MQ==",
            "name": "Default"
          },
          {
            "id": "U2hpcHBpbmdNZXRob2Q6MTU=",
            "name": "FedEx"
          },
          {
            "id": "U2hpcHBpbmdNZXRob2Q6MTQ=",
            "name": "UPS"
          },
          {
            "id": "U2hpcHBpbmdNZXRob2Q6MTY=",
            "name": "EMS"
          },
          {
            "id": "U2hpcHBpbmdNZXRob2Q6MTM=",
            "name": "DHL"
          }
        ]
      },
      "errors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 1,
      "maximumAvailable": 50000
    }
  }
}
```

7

<a id="select-shipping-method"></a>

### Select shipping method

Let's select the default shipping method for the checkout retrieved in the previous step.

**GraphQL**

```graphql
mutation {
  checkoutDeliveryMethodUpdate(
    id: "Q2hlY2tvdXQ6NmUzMzRlMjMtOWJiNS00MWUwLTk1NzYtZDc0NmJmMzQ4NDc1"
    deliveryMethodId: "U2hpcHBpbmdNZXRob2Q6MQ=="
  ) {
    errors {
      field
      message
    }
  }
}
```

**Result**

```json
{
  "data": {
    "checkoutDeliveryMethodUpdate": {
      "errors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 0,
      "maximumAvailable": 50000
    }
  }
}
```

8

<a id="add-webhook"></a>

### Add webhook

Before placing an order, we would like to add a webhook to notify our system about the order so we can dispatch email, create an export to the ERP system, or any other custom action.

For simplicity of the steps, let's do it via the dashboard:

-   Open Saleor dashboard
-   Go to _Extentions_ -> _Add Extention_ -> _Provide details manually_
-   App name `My App`
-   Enable Permissions `HANDLE_CHECKOUT` and `MANAGE_ORDERS`
-   Copy generated token somewhere safe
-   Create a webhook with the following details:
    -   Name: `Order webhook`
    -   Target URL: For simplicity of the test go to `https://webhook.site/` and copy `Your unique URL` value
    -   App: `My App`
    -   Select Event: Asynchronous -> `ORDER` -> `CREATED`

**Subscription query**

```graphql
subscription {
  event {
    ... on OrderCreated {
      __typename
      order {
        id
        shippingAddress {
          streetAddress1
          city
          cityArea
          companyName
          countryArea
          firstName
          country {
            country
            code
          }
          id
          lastName
          postalCode
          phone
        }
      }
    }
  }
}
```

9

<a id="create-an-order"></a>

### Create an order

For simplicity of the steps, we will skip the [payment step](/developer/payments/overview.md) and create an order directly.

Creating an order without payment requires admin or app permission. Add the app token copied in the previous step to the headers in the playground. If you forgot or lost a token from the previous step, you can create another one in the app settings.

After executing the mutation, you should be able to see the order in the dashboard and a payload in the [https://webhook.site/](https://webhook.site/) with the order details provided in the subscription query.

Note: [`orderCreateFromCheckout`](/api-reference/orders/mutations/order-create-from-checkout.md) mutation requires the App Token. To transform the checkout on behalf of the customer, use [`checkoutComplete`](/api-reference/checkout/mutations/checkout-complete.md) mutation.

**Headers**

```json
{
  "authorization": "Bearer YOUR_TOKEN"
}
```

**Mutation**

```graphql
mutation {
  orderCreateFromCheckout(
    id: "Q2hlY2tvdXQ6NmUzMzRlMjMtOWJiNS00MWUwLTk1NzYtZDc0NmJmMzQ4NDc1"
  ) {
    order {
      id
      status
      lines {
        variantName
        quantity
      }
    }
    errors {
      field
      message
    }
  }
}
```

**Result**

```json
{
  "data": {
    "orderCreateFromCheckout": {
      "order": {
        "id": "T3JkZXI6MzYwNWE2MWYtNDc4OC00ZjZlLWJiYzktZjEwMjJhOGFlOGYw",
        "status": "UNCONFIRMED",
        "lines": [
          {
            "variantName": "S",
            "quantity": 1
          }
        ]
      },
      "errors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 1,
      "maximumAvailable": 50000
    }
  }
}
```

10

<a id="marking-order-as-paid"></a>

### Marking order as paid

We can mark the order as paid manually in the dashboard, but let's proceed via API, as the main feature of headless API is that we can automate anything via API.

**Mutation**

```graphql
mutation {
  orderMarkAsPaid(
    id: "T3JkZXI6MzYwNWE2MWYtNDc4OC00ZjZlLWJiYzktZjEwMjJhOGFlOGYw"
  ) {
    order {
      id
      totalBalance {
        amount
      }
    }
  }
}
```

**Result**

```graphql
{
  "data": {
    "order": {
      "id": "T3JkZXI6MzYwNWE2MWYtNDc4OC00ZjZlLWJiYzktZjEwMjJhOGFlOGYw",
      "totalBalance": {
        "amount": 0
      }
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 1,
      "maximumAvailable": 50000
    }
  }
}
```

<a id="next-steps"></a>

## Next steps

To learn more about Saleor APIs interactively, you can inspect GraphQL operations using the browser's native network inspector or [GraphQL inspector](https://github.com/warrenday/graphql-network-inspector) while using the dashboard.

-   Explore [GraphQL tools](/api-usage/developer-tools.md)
-   Learn more about [Saleor's GraphQL API](/api-usage/overview.md) usage.

<a id="useful-resources"></a>

## Useful resources

<a id="postman"></a>

### Postman

Official [postman](https://www.postman.com/saleor-official) profile of Saleor's QA team. You can **fork** [collections](https://www.postman.com/saleor-official?tab=collections) and [flows](https://www.postman.com/saleor-official?tab=flows) which are useful for testing and exploring Saleor APIs.
