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

# Authentication

> Authenticate WiseYield API requests with API keys

<Note>
  **API-key surface (2026-05-18)** — API keys authenticate the **versioned `/api/v1/*`** surface. **51 endpoints across 7 scope families** are live today (Farms, Crops, Fields/Blocks/Plants, Tasks/Recurring/Templates, Crop Library, Analytics, Market Intelligence). Key minting is **Summit-tier only**. Remaining batches (AI, Billing, Team, User Profile) are queued — pages for those endpoints are noindexed until the routes ship. Track the [API Reference](/api-reference/introduction) for the current list.
</Note>

## API keys

WiseYield API keys are bearer tokens scoped to a single user and a set of capabilities. They are issued from the WiseYield dashboard and never round-trip through any other system.

### Creating a key

1. Sign in to your [WiseYield dashboard](https://www.wiseyield.co/dashboard).
2. Open **Settings → API Keys**.
3. Click **Create API Key**.
4. Give it a descriptive name (e.g. "Production server", "Mobile app").
5. Choose **Environment**: `live` or `test`.
6. Pick the **scopes** the key needs (least-privilege).
7. Copy the key immediately — the full secret is shown **once** and never again.

<Warning>
  Each user is limited to **10 active API keys** at a time. Revoke unused keys before creating new ones.
</Warning>

### Key format

```
wy_{environment}_{48_hex_chars}
```

Examples:

```
wy_live_a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890
wy_test_0f1e2d3c4b5a69870f1e2d3c4b5a69870f1e2d3c4b5a6987
```

* Prefix: `wy_` (identifies WiseYield).
* Environment: `live_` (production data) or `test_` (test data).
* Random: 48 hexadecimal characters (24 bytes from a cryptographic RNG).

Keys are stored as a SHA-256 hash. Once created, the cleartext value cannot be retrieved — only the prefix and a usage summary are visible in the dashboard.

## Making authenticated requests

Send the API key in the `Authorization` header using the `Bearer` scheme:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const WISEYIELD_API_KEY = process.env.WISEYIELD_API_KEY;

  const response = await fetch('https://www.wiseyield.co/api/v1/farms', {
    headers: {
      'Authorization': `Bearer ${WISEYIELD_API_KEY}`,
      'Content-Type': 'application/json'
    }
  });
  ```

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

  WISEYIELD_API_KEY = os.environ['WISEYIELD_API_KEY']

  response = requests.get(
      'https://www.wiseyield.co/api/v1/farms',
      headers={
          'Authorization': f'Bearer {WISEYIELD_API_KEY}',
          'Content-Type': 'application/json'
      }
  )
  ```

  ```bash cURL theme={null}
  curl https://www.wiseyield.co/api/v1/farms \
    -H "Authorization: Bearer $WISEYIELD_API_KEY" \
    -H "Content-Type: application/json"
  ```
</CodeGroup>

## Scopes

API keys carry a list of scopes. Requests are rejected with `403 Forbidden` (`INSUFFICIENT_SCOPE`) when a key is missing the scope required by an endpoint.

| Scope            | Grants                                                  |
| ---------------- | ------------------------------------------------------- |
| `farms:read`     | List and read farms                                     |
| `farms:write`    | Create, update, and delete farms                        |
| `crops:read`     | List and read crops                                     |
| `crops:write`    | Create, update, and delete crops                        |
| `fields:read`    | List and read fields, blocks, and per-plant records     |
| `fields:write`   | Create, update, and delete fields, blocks, and plants   |
| `library:read`   | Browse the crop library (admin catalog + own shortlist) |
| `library:write`  | Add and remove crops from your shortlist                |
| `market:read`    | Read anonymized regional crop prices                    |
| `analytics:read` | Read analytics, predictions, and reports                |
| `tasks:read`     | List and read tasks                                     |
| `tasks:write`    | Create, update, and complete tasks                      |
| `team:read`      | List farm members and invitations                       |
| `team:write`     | Invite, update, and remove farm members                 |
| `webhooks:read`  | List configured webhooks                                |
| `webhooks:write` | Create and update webhooks                              |
| `all`            | Full access to every resource the user owns             |

### Example: read-only integration

```json theme={null}
{
  "name": "BI Dashboard",
  "environment": "live",
  "scopes": ["farms:read", "crops:read", "analytics:read"]
}
```

## Rate limits

Rate limits are applied **per user**, sliding-window, by subscription tier. The system fails closed: if the rate-limit backend is unreachable in production, requests are denied.

| Tier            | Limit               |
| --------------- | ------------------- |
| Expired trial   | 3 requests/hour     |
| Seed            | 25 requests/hour    |
| Sprout          | 50 requests/hour    |
| Trial / Harvest | 100 requests/hour   |
| Grove           | 200 requests/hour   |
| Summit          | 1,000 requests/hour |

A 14-day trial grants Harvest-level access. Billing currency is auto-detected per the buyer's country at checkout (EUR-denominated, via Dodo Adaptive Currency).

### Rate-limit headers

Every response (success or rate-limited) carries the current window state:

```http theme={null}
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 2026-05-17T14:00:00.000Z
```

* `X-RateLimit-Limit` — total requests allowed in the current window.
* `X-RateLimit-Remaining` — requests remaining before the limit kicks in.
* `X-RateLimit-Reset` — **ISO 8601 timestamp** when the sliding window opens again.

### When you hit the limit

```http theme={null}
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-05-17T14:00:00.000Z

{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED"
}
```

Implement exponential backoff or schedule retries against `X-RateLimit-Reset`:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function fetchWithBackoff(url, init, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url, init);
      if (response.status !== 429) return response;

      const resetIso = response.headers.get('X-RateLimit-Reset');
      const waitMs = resetIso
        ? Math.max(0, new Date(resetIso).getTime() - Date.now())
        : 2 ** attempt * 1000;
      await new Promise(r => setTimeout(r, waitMs));
    }
    throw new Error('Max retries exceeded');
  }
  ```

  ```python Python theme={null}
  import time
  import requests
  from datetime import datetime, timezone

  def fetch_with_backoff(url, headers, max_retries=3):
      for attempt in range(max_retries):
          response = requests.get(url, headers=headers)
          if response.status_code != 429:
              return response

          reset_iso = response.headers.get('X-RateLimit-Reset')
          if reset_iso:
              reset = datetime.fromisoformat(reset_iso.replace('Z', '+00:00'))
              wait = max(0, (reset - datetime.now(timezone.utc)).total_seconds())
          else:
              wait = 2 ** attempt
          time.sleep(wait)
      raise RuntimeError('Max retries exceeded')
  ```
</CodeGroup>

## Error responses

Authentication and authorization errors share a common shape:

```json theme={null}
{
  "error": "Unauthorized",
  "message": "Invalid or revoked API key",
  "code": "INVALID_API_KEY"
}
```

| Status | `code`                   | Meaning                                                |
| ------ | ------------------------ | ------------------------------------------------------ |
| 401    | `MISSING_API_KEY`        | No `Authorization` header sent                         |
| 401    | `INVALID_AUTH_FORMAT`    | Header is not `Bearer <key>`                           |
| 401    | `INVALID_API_KEY_FORMAT` | Key doesn't match `wy_(live\|test)_[a-f0-9]{48}`       |
| 401    | `INVALID_API_KEY`        | Key not found or has been revoked                      |
| 401    | `INACTIVE_API_KEY`       | Key exists but is currently disabled                   |
| 401    | `EXPIRED_API_KEY`        | Key passed its `expiresAt` timestamp                   |
| 403    | `IP_NOT_WHITELISTED`     | Request came from an IP not in the key's allowlist     |
| 403    | `INSUFFICIENT_SCOPE`     | Key is valid but missing the scope this route requires |
| 429    | `RATE_LIMIT_EXCEEDED`    | Per-user rate limit reached                            |

## Security best practices

<AccordionGroup>
  <Accordion title="Store keys securely">
    Never hard-code keys. Use environment variables or a secrets manager.

    ```bash theme={null}
    # .env (add to .gitignore)
    WISEYIELD_API_KEY=wy_live_...
    ```
  </Accordion>

  <Accordion title="Separate keys per environment">
    Use distinct keys for development, staging, and production. Scope them tightly and rotate them independently.
  </Accordion>

  <Accordion title="Restrict by IP when possible">
    When creating a key you can set an IP allowlist in `metadata.ipWhitelist`. Requests from any other origin return `403 IP_NOT_WHITELISTED`.
  </Accordion>

  <Accordion title="Rotate on a schedule">
    Rotate keys at least every 90 days, and immediately when:

    * Someone with access leaves the team
    * A key may have been exposed (logs, repos, screenshots)
    * Compliance requires it
  </Accordion>

  <Accordion title="Monitor usage">
    The dashboard shows last-used timestamp, request volume, and error rate per key. Investigate unfamiliar patterns or sudden spikes.
  </Accordion>
</AccordionGroup>

## Revoking a key

If a key is compromised or no longer needed:

1. Go to **Settings → API Keys**.
2. Find the key by name or prefix.
3. Click **Revoke**.

Revoked keys are invalidated immediately. Any client still presenting the key will start receiving `401 INVALID_API_KEY`.

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="play" href="/quickstart">
    Make your first authenticated request
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Browse every endpoint
  </Card>
</CardGroup>
