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

# API Authentication: Keys, Environments, and Errors

> Authenticate every PowerHQ API request with your API key: one static secret in the x-api-key header, no OAuth handshake, no token to refresh.

Authentication is a single step: put your **API key** in the `x-api-key` header. That is the whole model. There is no OAuth exchange and no token to refresh.

## Your API Key

Pass your API key in the `x-api-key` HTTP header on every request. The key is a static secret issued by your PowerHQ representative; treat it like a password and never expose it in client-side code or public repositories.

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST https://eapi.prod.powerhq.co/graphql \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"query": "{ utilities(zipCode: \"75231\") { id name } }"}'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://eapi.prod.powerhq.co/graphql",
      headers={
          "x-api-key": "YOUR_API_KEY",
          "Content-Type": "application/json",
      },
      json={"query": "{ utilities(zipCode: \"75231\") { id name } }"},
  )

  data = response.json()
  print(data)
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://eapi.prod.powerhq.co/graphql", {
    method: "POST",
    headers: {
      "x-api-key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: `{ utilities(zipCode: "75231") { id name } }`,
    }),
  });

  const data = await response.json();
  console.log(data);
  ```
</CodeGroup>

## Where You Can Call From

Anything that can make an HTTPS request and set a header can call the API:

* Serverless and edge runtimes with rotating egress IPs (Lambda, Cloudflare Workers, Vercel)
* Containers and autoscaling groups whose IPs change on every deploy
* CI pipelines and local development machines

Because the key is the only credential, keep it server-side. Never ship it in a browser bundle, a
mobile app, or a public repository. If you need something a browser can call, put a thin proxy of
your own in front of it and keep the key on the proxy.

<Tip>
  Want to try the API before you have a key at all? The [playground](/playground) runs real queries
  against production with no key and no signup.
</Tip>

## Environments

Both environments enforce the same authentication requirements. Use Certification while you build and test; switch to Production for live customer traffic.

| Environment             | Endpoint                               | Intended Use                             |
| ----------------------- | -------------------------------------- | ---------------------------------------- |
| Production              | `https://eapi.prod.powerhq.co/graphql` | Live traffic serving real customers      |
| Certification (staging) | `https://eapi.cert.powerhq.co/graphql` | Development, integration testing, and QA |

<Tip>
  Point your integration at the Certification environment during development. It lets you iterate safely without risking any impact to production data or customer-facing systems.
</Tip>

Onboarding asks for the IP addresses your team will be testing from, so we can enable them for the
certification checkout flow. Sending them early keeps your first end-to-end test from waiting on us.

## Error Responses

### 403 Forbidden

<Warning>
  A `403 Forbidden` response means the `x-api-key` header is missing, malformed, or not a valid key for that environment. Check that the header is present, that the key has no stray whitespace, and that you are using the right key for the environment you are calling — staging and production keys are different.
</Warning>

### 400 Bad Request

A `400 Bad Request` response indicates a malformed HTTP request body — for example, invalid JSON or a missing `Content-Type: application/json` header. Check that your request body is valid JSON and that the `Content-Type` header is set correctly.

### GraphQL Errors (HTTP 200)

GraphQL errors — such as unknown fields, missing required arguments, or resolver-level failures — are returned with an HTTP status of `200`. The response body contains an `errors` array alongside (or instead of) the `data` object:

```json theme={null}
{
  "errors": [
    {
      "message": "Field 'unknownField' doesn't exist on type 'Plan'",
      "locations": [{ "line": 1, "column": 42 }],
      "path": ["residentialPlans", 0, "unknownField"]
    }
  ]
}
```

For a full list of GraphQL error codes and how to handle them, see the [Errors reference](/reference/errors).
