Getting Started

We have designed the API to follow GraphQL conventions: fields are named in camelCase, types are named in PascalCase and enums are named in PascalCase with values in ALL CAPS. Shopify's own GraphQL APIs also follow this same pattern.

Compare this with our RESTful APIs, which adopt snake_case for fields in both requests and responses.

The GraphQL endpoint

Unlike the REST Storefront API, which has a variety of endpoints, the GraphQL Admin API has a single endpoint:

POST https://submarine.discolabs.com/admin/api/v1/graphql.json

Authentication

In order to secure communication between API clients and the Submarine GraphQL API, both the clients and server will interact via HTTPS and will implement the widely adopted Hash-based Message Authentication Code (HMAC) authentication method. Using this methodology ensures data integrity, validates request origin and prevents replay requests.

In brief, HMAC authentication relies on the server issuing a unique API client ID and a unique shared secret to each client. The API client ID is used to identify the client from which a request originates and may also be used to scope access to API features. The shared secret is a key available to both the client and server and is used by the client to sign the request and by the server to validate the authenticity of said request.

The client should use the secret to build a request signature, which should be sent with the API client ID as an Authorization request header:

Authorization: hmacauth {api_client_id}:{signature}

The signature is simply a base64-encoded version of the request payload, signed using HMAC-SHA256. An example Ruby implementation:

calculated_hmac = OpenSSL::HMAC.digest('sha256', 'shared-secret', request_body_as_string)

Base64.strict_encode64(calculated_hmac)

Invalid requests will return a 401 error:

{
  "errors": [
    {
      "message": "Could not authenticate request",
      "extensions": {
        "code": "InvalidSignatureError"
      }
    }
  ]
}

Query the GraphQL Admin API

You can access the GraphQL Admin API endpoint using cURL or any other HTTP client.

Example query
Retrieving details of an order.

Request:

{
  order(id: "gid://submarine/Order/1835878973539") {
    customer {
      email
    }
    id
    lineItems {
      quantity
      sku
    }
    name
    totalPrice {
      amount
      currencyCode
    }
  }
}

Response:

{
  "data": {
    "order": {
      "customer": {
        "email": "[email protected]",
      },
      "id": "gid://submarine/Order/1835878973539",
      "lineItems": [
        {
          "quantity": 1,
          "sku": "10745A"
        },
        {
          "quantity": 2,
          "sku": "90001A"
        }
      ],
      "name": "#34043",
      "totalPrice": {
        "amount": 9.9,
        "currencyCode": "AUD"
      }
    }
  }
}

📘

Bad requests

Errors caused by malformed or invalid requests will usually be returned in the userErrors field of the response payload. Higher-level errors, such as issues with authentication, will be surfaced in a top-level errors key.

Example mutation: orderCalculate
Calculating an order.

Request:

mutation orderCalculate($input: CalculatedOrderInput!) {
  orderCalculate(input: $input) {
    calculatedOrder {
      availableShippingRates {
        handle
        price {
          amount
          currencyCode
        }
        title
      }
      customer {
        email
      }
      customerPaymentMethods {
        default
        id
        sourceId
        type
      }
		}
    userErrors {
      field
      messages
    }
  }
}

Input:

{
	"input": {
		"customerId": "gid://shopify/Customer/754119508038",
		"lineItems": [
			{
				"quantity": 2,
				"variantId": "gid://shopify/ProductVariant/18390473867334"
			}
		],
		"shippingAddress": {
      "first_name": "David",
			"last_name": "Bird",
      "company": "Disco Labs",
			"address1": "36-38 Gipps Street",
			"city": "Collingwood",
			"countryCode": "AU",
			"provinceCode": "VIC",
			"zip": "3066"
		}
	}
}

Good response:

{
  "data": {
    "orderCalculate": {
      "calculatedOrder": {
        "availableShippingRates": [
          {
            "handle": "shopify-Free%20shipping-0.00",
            "price": {
              "amount": 0.0,
              "currencyCode": "AUD"
            },
            "title": "Free shipping"
          },
          {
            "handle": "shopify-Standard%20Shipping-10.00",
            "price": {
              "amount": 10.0,
              "currencyCode": "AUD"
            },
            "title": "Standard Shipping"
          }
        ],
        "customer": {
          "email": "[email protected]"
        },
        "customerPaymentMethods": [
          {
            "default": false,
            "id": "gid://submarine/CustomerPaymentMethod/90836",
            "sourceId": "350297",
            "type": "invoice"
          },
          {
            "default": true,
            "id": "gid://submarine/CustomerPaymentMethod/90837",
            "sourceId": "350300",
            "type": "card"
          }
        ]
			},
      "userErrors": []
    }
  }
}

Bad response:

{
  "data": {
    "orderCalculate": {
      "calculatedOrder": null,
      "userErrors": [
        {
          "field": "customerId",
          "messages": [
            "Cannot find customer: 754119508038"
          ]
        }
      ]
    }
  }
}

Example mutation: orderCreate
Using the output of the orderCalculate mutation to create a Shopify order.

Request:

mutation orderCreate($input: OrderInput!) {
  orderCreate(input: $input) {
    order {
      createdAt
      name
      shippingLine {
        price {
          amount
          currencyCode
        }
        title
      }
      shopifyId
      totalPrice {
        amount
        currencyCode
      }
    }
    userErrors {
      field
      messages
    }
  }
}

Input:

{
	"input": {
		"billingAddress": {
      "first_name": "David",
			"last_name": "Bird",
      "company": "Disco Labs",
			"address1": "36-38 Gipps Street",
			"city": "Collingwood",
			"countryCode": "AU",
			"provinceCode": "VIC",
			"zip": "3066"
		},
		"customerId": "gid://shopify/Customer/754119508038",
		"lineItems": [
			{
				"quantity": 2,
				"variantId": "gid://shopify/ProductVariant/18390473867334"
			}
		],
		"paymentMethodId": "gid://submarine/CustomerPaymentMethod/90837",
		"shippingAddress": {
      "first_name": "David",
			"last_name": "Bird",
      "company": "Disco Labs",
			"address1": "36-38 Gipps Street",
			"city": "Collingwood",
			"countryCode": "AU",
			"provinceCode": "VIC",
			"zip": "3066"
		},
		"shippingLine": {
			"shippingRateHandle": "shopify-Standard%20Shipping-10.00"
		}
	}
}

Good response:

{
  "data": {
    "orderCreate": {
      "order": {
        "createdAt": "2020-07-09T21:37:12Z",
        "id": "gid://submarine/Order/2227720257606",
        "name": "#1117",
        "shippingLine": {
          "price": {
            "amount": 10.0,
            "currencyCode": "AUD"
          },
          "title": "Standard Shipping"
        },
        "shopifyId": "gid://shopify/Order/2227720257606",
        "totalPrice": {
          "amount": 41.9,
          "currencyCode": "AUD"
        }
      },
      "userErrors": []
    }
  }
}

Bad response:

{
  "data": {
    "orderCalculate": {
      "calculatedOrder": null,
      "userErrors": [
        {
          "field": "paymentMethodId",
          "messages": [
            "Cannot find default payment method"
          ]
        }
      ]
    }
  }
}

Example mutation: draftOrderComplete
Specifying an existing draft order to complete and the payment method to complete it with.

Request:

mutation draftOrderComplete($input: CompletableDraftOrderInput!) {
  draftOrderComplete(input: $input) {
    completedDraftOrder {
      orderId
      orderName
      processorReference
      submarineReference
    }
    userErrors {
      field
      messages
    }
  }
}

Input:

{
  "input": {
    "draftOrderId": "gid://shopify/DraftOrder/3847825202",
    "paymentMethodId": "gid://submarine/AuthorizedPaymentMethod/90837"
  }
}

Good response:

{
  "data": {
    "draftOrderComplete": {
      "completedDraftOrder": {
        "orderId": "gid://shopify/Order/2227720257606",
        "orderName": "#49274",
        "processorReference": "ch_1DGJF3JqrrHX7MBOBXWbkO6q",
        "submarineReference": "e55ef486-aa71-4e48-ae7f-e40a27cd0761"
      },
      "userErrors": []
    }
  }
}

Bad response (error with input):

{
  "data": {
    "draftOrderComplete": {
      "orderId": null,
      "orderName": null,
      "processorReference": null,
      "submarineReference": null,
      "userErrors": [
        {
          "field": "paymentMethodId",
          "messages": [
            "Cannot find specified payment method"
          ]
        }
      ]
    }
  }
}

Bad response (payment failed):

{
  "data": {
    "draftOrderComplete": {
      "orderId": null,
      "orderName": null,
      "processorReference": null,
      "submarineReference": null,
      "userErrors": [
        {
          "field": "paymentMethodId",
          "messages": [
            "FAILED: Your card was declined. [4566185e-b79f-45e7-8432-e17f456ecb19]"
          ]
        }
      ]
    }
  }
}

Bad response (payment rate limited):

{
  "data": {
    "draftOrderComplete": {
      "orderId": null,
      "orderName": null,
      "processorReference": null,
      "submarineReference": null,
      "userErrors": [
        {
          "field": "paymentMethodId",
          "messages": [
            "FAILED: Too many requests."
          ]
        }
      ]
    }
  }
}

Bad response (payment succeeded but draft order completion failed):

{
  "data": {
    "draftOrderComplete": {
      "orderId": null,
      "orderName": null,
      "processorReference": "ch_1DGJF3JqrrHX7MBOBXWbkO6q",
      "submarineReference": "e55ef486-aa71-4e48-ae7f-e40a27cd0761",
      "userErrors": [
        {
          "field": "draftOrderId",
          "messages": [
            "Could not complete draft order"
          ]
        }
      ]
    }
  }
}