> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shoply.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Secret API keys: how one is sent, what it reaches, and what protects it

Every request carries a secret API key as a bearer token, but for the Customer API's, whose token is a buyer's ([below](#a-buyers-token)).

```bash theme={null}
curl https://api.shoply.gg/v1/key \
  -H "Authorization: Bearer shoply_sk_..."
```

Where a client cannot set that header, the key may go in `X-Api-Key` instead.

## What a key is

A key is made in the dashboard, under **Developers**, then **API Keys**. It starts `shoply_sk_`, and it is shown once: Shoply keeps a hash of it and cannot show it again. A store holds up to 15 live keys.

A key belongs to **one store** and reaches that store only. Asked about any other store, it is told there is no such store, whether or not the other store is yours. An account with several stores makes a key in each.

## What a key carries

**Permissions** `required`

What the key may do, ticked when it is made: read products, refund orders, and so on. A route is refused to a key that does not carry its permission, with `missing_scope` and the name of the permission it needed. See [Permissions](/api-reference/permissions.md).

**Allowed addresses** `optional`

The IP addresses, or ranges in CIDR form, the key may be used from. Up to 20.

**Allowed domains** `optional`

Domains the key may be used from, such as `server.example.com`. Up to 10. A domain stands for the addresses it points at: Shoply looks the name up, and a request is let through when it comes from one of them.

**A last day** `optional`

A date after which the key stops working by itself. Left empty, it works until it is revoked.

A key's name, permissions, addresses and domains can be changed afterwards, and its secret can be replaced with a new one: the old secret stops working at once and everything else about the key stays. Changing what a key may do or where it may be used from, and replacing its secret, each ask whoever does it to confirm it is them, and the store's owner is told by email.

## Where a key may be used from

A key with no addresses and no domains works from anywhere. Give it either and it is held to them: a request is let through when it comes from an address on the list, or from one a listed domain points at. From anywhere else it is refused with `ip_not_allowed`, and still written into the store's record of requests, so you can see that the key was tried from somewhere it should not be.

Three things about domains:

* **A domain is judged by where the request comes from.** Never by an `Origin` or `Referer` header: those are written by the sender, and whoever holds a key can write them.
* **It has to point at the machine that sends the requests.** A domain behind a proxy such as Cloudflare points at the proxy, not at your server, so a key held to it would be refused everywhere. The dashboard says so when you type one. Use a name that points straight at the server, or the server's IP address.
* **A change to its DNS record is followed within five minutes.** If the name cannot be looked up for a while, the addresses it last pointed at go on being accepted, for up to a day.

## Keeping one safe

* **On a server, never in a browser or an app.** The API sends no CORS headers, so a page cannot read its answers at all. Whatever a browser has, its user has.
* **One key for each thing that uses one.** Then one can be revoked without stopping the others, and the record of requests says which did what.
* **Only the permissions the job needs**, and its server's address or domain if you can name one.
* **Revoke a key the moment it may have been seen**, and make another. Revoking is immediate.

## When a key is refused

| Status | `code` | What it means |
| - | - | - |
| 401 | `api_key_required` | No key was sent. |
| 401 | `invalid_api_key` | The key is not a live one: mistyped, revoked, or past its last day. |
| 403 | `missing_scope` | The key does not carry the permission the route needs. `requiredScope` names it. |
| 403 | `ip_not_allowed` | The request came from an address the key does not accept: not on its list, and not one its domains point at. |
| 403 | `account_unavailable` | The store's account cannot use the API. Sign in to the dashboard to see why. |
| 403 | `dashboard_only` | This is something only a person in the dashboard may do. |
| 404 | `not_found` | The store in the address is not the key's own. |

An address that sends a run of keys that are not keys, or of requests with a key that does not accept that address, is turned away for a few minutes with `rate_limited`.

## An app's token

An app another developer built, which a seller connected to their store by [OAuth](/guides/oauth.md), is handed a token that starts `shoply_at_`. It is a key of that store, sent the same way: `Authorization: Bearer shoply_at_...`. It carries the permissions the seller approved, works from anywhere, and stops when they disconnect the app. [Get the current key](/api-reference/api-key/get-the-current-key.md) names the app in `app`. It is not among the store's 15 keys and is not on the **API Keys** page.

## A buyer's token

The routes of the [Customer API](/guides/customer-api.md) take no API key. A buyer signs in with a code emailed to them and is handed a token of their own, `shoply_ct_...`, sent the same way: `Authorization: Bearer shoply_ct_...`. It reaches that buyer's own orders at one store, and no route that takes a key. A key reaches none of its routes either.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.