Using the Galley API

How to use Galley's GraphQL API to access and edit the data in your account.

Overview

This article provides a comprehensive guide to using Galley's GraphQL Open API, with a focus on querying recipes and menus using connection API calls. The manual includes step-by-step instructions and examples to help you get started with the API, leveraging the GraphQL playground for exploration and testing.

Getting Started

The Galley GraphQL API is accessible via HTTPS POST at https://api.galleysolutions.com/graphql. There is a GraphQL Playground available at the same URL that you can use in your browser to execute queries against the API.

Authentication & Authorization

Galley supports two types of API keys that can be used for authentication. The same key can be used to make requests to both the Production and Staging environments.

1. Full-access, Read & Write Keys

These keys use an x-api-key header to authenticate and have admin-level permissions, meaning you can execute all mutations and queries available to a normal admin user in the web-app.

2. User-scoped, Read-only Keys

These keys use an x-user-api-key header to authenticate, and have admin-level, but read-only, permissions.

Your key should be the only additional header provided in the API POST request, formatted as:

{ “x-api-key”: “<your api key>” }

OR

{ “x-user-api-key”: “<your api key>” }

Please reach out to support@galleysolutions.com or in the in-app chat to obtain your API key. Keys can be refreshed upon request.

Rate Limits

To ensure the quality and stability of Galley's services, rate limiting is enforced on calls to the Galley API. The limit is set to 600 requests per 5-minute period from each unique IP address.

Exceeding the allotment will result in a 403 response with the message "This request had been blocked due to rate limits." If the error is received, please wait to make additional requests until the appropriate amount of time has passed and try again.

When writing a script that may exceed this number of requests, we recommend implementing a delay of 0.5 seconds between each request to ensure the limit is not reached.

Pagination

The API enforces pagination limits with a max size of 25 results per page on most API queries. This includes viewer.recipeConnection, viewer.menuConnection and any other connection query. The examples below show how the paginationOptions argument can be used to easily loop through all results.

Any script using one of the queries above with a limit greater than 25 will result in an error while any script without a limit set will automatically have a limit of 25.

UUIDs

Galley uses UUIDs to uniquely identify entities in the system. Every entity has an id field that can be considered unique. UUIDs are not expected to change once an entity is created, and users will be notified in advance if there is going to be a change.

In the web app, you can determine the UUID of an entity from the URL. For example, if you've clicked into a specific menu, the UUID of the menu would be in the URL as /menus/<uuid of menu>.

Error Handling

Most mutations have an optional error field that can be requested when making API calls. If an error occurs from the call, the error.message can provide additional detail into the cause.

Support Tools

Production & Staging Playgrounds

The GraphQL playground is an interactive, graphical interface that allows you to explore the API's schema, construct queries, and view responses. It is an essential tool for understanding and testing the Galley API.
To use the playground:
  1. Navigate to the appropriate URL (production or staging).
  2. Use the left pane to write your GraphQL queries.
  3. Click the "Play" button to execute the query.
  4. View the response in the right pane.
The playground also provides schema introspection, allowing you to explore the available types, fields, and operations supported by the Galley API.

In-App Queries

All functionality within the Galley web-app is powered by the GraphQL API. You can freely inspect all GraphQL requests the web app makes to learn how to replicate the functionality with API calls.

Domain Visualization

We also have a visualization of the Galley domain to help you navigate the playground docs.

Common Queries

recipeConnection

Recipes are a core component of the Galley API. The recipeConnection API call allows you to retrieve a paginated list of recipes efficiently, which is particularly useful when dealing with large datasets.

Query:

query fetchAllRecipes($paginationOptions: PaginationOptions) {
  viewer {
    recipeConnection(paginationOptions: $paginationOptions) {
      edges {
        node {
          id
          name
          totalYield
          yieldUnit {
            id
            name
          }
        }
      }
      totalCount
      pageInfo {
        hasNextPage
        startIndex
        endIndex
      }
    }
  }
}

Variables:

{
  "paginationOptions": {
    "first": 25,
    "startIndex": 0
  }
}

Sample Response:

{
  "data": {
    "viewer": {
      "recipeConnection": {
        "edges": [
          {
            "node": {
              "id": "cmVjaXBlOjcxMTgwNA==",
              "name": "Guacamole",
              "totalYield": 3900,
              "yieldUnit": {
                "id": "dW5pdDox",
                "name": "g"
              }
            }
          },
        //... 24 more recipes
        ],
        "totalCount": 33,
        "pageInfo": {
          "hasNextPage": true,
          "startIndex": 0,
          "endIndex": 25
        }
      }
    }
  }
}

menuConnection

Menus are collections of recipes grouped together. Similar to recipes, you can use connection API calls to retrieve a paginated list of menus using the menuConnection query.

You can also use the filters on any connection call to filter down the list to specific results.

Query:

query fetchLunchMenus($paginationOptions: PaginationOptions, $filters: MenuConnectionFilter) {
  viewer {
    menuConnection(paginationOptions: $paginationOptions, filters: $filters) {
      edges {
        node {
          id
          name
          menuItems {
            recipe {
              name
            }
            volume
            unit {
              id
              name
            }
          }
        }
      }
      totalCount
      pageInfo {
        hasNextPage
        startIndex
        endIndex
      }
    }
  }
}

Variables:

{
  "paginationOptions": {
    "first": 25,
    "startIndex": 0
  },
  "filters": {
    "categoryValueIdsBooleanExpressionFilterJSON": {
    "and": ["<category value id>"]
    }
  }
}

Sample Response:

{
  "data": {
    "viewer": {
      "menuConnection": {
        "edges": [
          {
            "node": {
              "id": "bWVudTo0MTIyODA1",
              "name": "Lunch - Entree",
              "menuItems": [
                {
                  "recipe": {
                    "name": "Lemon Quinoa Salad with Chicken"
                  },
                  "volume": 100,
                  "unit": {
                    "id": "dW5pdDoyNDI5NzA3",
                    "name": "serving"
                  }
                },
                {
                  "recipe": {
                    "name": "Chicken Caesar Salad"
                  },
                  "volume": 100,
                  "unit": {
                    "id": "dW5pdDoyNDI5NzA4",
                    "name": "serving"
                  }
                },
                {
                  "recipe": {
                    "name": "Club Soda"
                  },
                  "volume": 100,
                  "unit": {
                    "id": "dW5pdDoyNDI5ODc4",
                    "name": "can"
                  }
                }
              ]
            }
          },
        //... 2 more menus
        ],
      "totalCount": 3,
        "pageInfo": {
        "hasNextPage": false,
          "startIndex": 0,
        "endIndex": 2
        }
      }
    }
  }
}

Common Mutations

upsertRecipe

The GraphQL API can also be used to send data to your Galley account. The upsertRecipe mutation is a common way to create or update recipe fields.

Mutation:

mutation updateRecipeMutation($input: UpsertRecipeInput!) {
  upsertRecipe(input: $input) {
    recipe {
      id
      name
      shelfLifeDays
    }
    error {
      message
    }
  }
}

Input:

{
  "input": {
    "recipe": {
    "id": "<recipe id>",
    "shelfLifeDays": <integer value>
    }
  }
}

Sample Response:

{
  "data": {
    "upsertRecipe": {
      "recipe": {
        "id": "cmVjaXBlOjcxMjA4OA==",
        "name": "Brazilian Cheese Bread (Pão de Queijo)",
        "shelfLifeDays": 3
      },
      "error": null
    }
  }
}

bulkAddCategoryValueItem

Bulk mutations can be useful when trying to write values to multiple entities at once. For example, the bulkAddCategoryValueItem mutation can be used to bulk tag multiple entities with the same category tag(s).

Mutation:

mutation BulkAddCategoryValueItems($input: BulkAddCategoryValueItemsInput!) {
  bulkAddCategoryValueItems(input: $input) {
    categoryValueItems {
      itemId
      itemType
      categoryValue {
        name
      }
    }
    error {
      message
    }
  }
}

Input:

{
  "input": {
  "itemIds": ["<menu 1 id>", "<menu 2 id>", "<menu 3 id>"],
  "categoryValueIds": ["<category value 1 id>", "<category value 2 id>"],
    "itemType": "menu"
  }
}

Sample Response:

{
  "data": {
    "bulkAddCategoryValueItems": {
      "categoryValueItems": [
        {
          "itemId": "bWVudTo0MTIyODA3",
          "itemType": "menu",
          "categoryValue": {
            "name": "buffet"
          }
        },
        {
          "itemId": "bWVudTo0MTIyODA3",
          "itemType": "menu",
          "categoryValue": {
            "name": "lunch"
          }
      },
      // ... 4 more category value items
      ],
      "error": null
    }
  }
}