> ## 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.

# Create API Key

> Generate a new API key for programmatic access to the WiseYield API

## Endpoint

```
POST https://www.wiseyield.co/api/api-keys
```

<Note>
  **Dashboard-only endpoint.** Called from the WiseYield UI with browser cookies (Clerk session). Not callable from a server-to-server integration today. To mint API keys, sign in to the [dashboard](https://www.wiseyield.co/dashboard) and use **Settings → API Keys → Create API Key**.
</Note>

<Warning>
  **Summit-tier only.** API key creation requires an active Summit subscription. Requests from users on Seed / Sprout / Harvest / Grove / Trial tiers return `403 TIER_INSUFFICIENT`. See [Subscription tiers](/concepts/tier-ladder) for what's included at each tier.
</Warning>

## Authentication

Requires a signed-in WiseYield session. The `Bearer` examples below show the response wire format but are not directly callable outside the browser session.

## Request Body

<ParamField body="name" type="string" required>
  User-friendly name for the API key (e.g., "Production Server", "Mobile App")
</ParamField>

<ParamField body="scopes" type="array" required>
  Array of permission scopes. At least one scope required.

  **Available Scopes**:

  * `farms:read`, `farms:write`
  * `crops:read`, `crops:write`
  * `fields:read`, `fields:write`
  * `library:read`, `library:write`
  * `market:read`
  * `analytics:read`
  * `tasks:read`, `tasks:write`
  * `team:read`, `team:write`
  * `webhooks:read`, `webhooks:write`
  * `all` (full access)
</ParamField>

<ParamField body="environment" type="string" default="live">
  Environment type: `live` (production) or `test` (development)
</ParamField>

<ParamField body="rateLimit" type="number" default="1000">
  Maximum API requests per hour (1-100,000)
</ParamField>

<ParamField body="expiresAt" type="string">
  ISO 8601 datetime when key should expire (optional, no expiration if omitted)
</ParamField>

<ParamField body="metadata" type="object">
  Custom metadata to attach to the key (optional)
</ParamField>

## Response

Returns the newly created API key object **with the full key**.

<ResponseField name="id" type="string">
  Unique identifier (format: `key_{nanoid}`)
</ResponseField>

<ResponseField name="key" type="string">
  **⚠️ CRITICAL**: Full API key (shown ONLY ONCE, never retrievable again)

  Format: `wy_{environment}_{48_hex_characters}`
</ResponseField>

<ResponseField name="keyPrefix" type="string">
  First 16 characters for identification
</ResponseField>

<ResponseField name="keyPreview" type="string">
  Masked version for display
</ResponseField>

<ResponseField name="name" type="string">
  Name provided in request
</ResponseField>

<ResponseField name="environment" type="string">
  Environment: `live` or `test`
</ResponseField>

<ResponseField name="scopes" type="array">
  Array of granted permission scopes
</ResponseField>

<ResponseField name="rateLimit" type="number">
  Requests per hour limit
</ResponseField>

<ResponseField name="isActive" type="boolean">
  Always `true` on creation
</ResponseField>

<ResponseField name="usageCount" type="number">
  Always `0` on creation
</ResponseField>

<ResponseField name="lastUsedAt" type="string">
  Always `null` on creation
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO 8601 timestamp of creation
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 timestamp of last update
</ResponseField>

<ResponseField name="expiresAt" type="string">
  Expiration timestamp (null if no expiration)
</ResponseField>

<ResponseField name="metadata" type="object">
  Custom metadata (includes userAgent automatically)
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://www.wiseyield.co/api/api-keys \
    -H "Authorization: Bearer {CLERK_SESSION_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Production API Key",
      "environment": "live",
      "scopes": ["farms:read", "farms:write", "crops:read", "crops:write"],
      "rateLimit": 5000,
      "metadata": {
        "application": "web-dashboard",
        "version": "1.0.0"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://www.wiseyield.co/api/api-keys',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${clerkSessionToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        name: 'Production API Key',
        environment: 'live',
        scopes: ['farms:read', 'farms:write', 'crops:read', 'crops:write'],
        rateLimit: 5000,
        metadata: {
          application: 'web-dashboard',
          version: '1.0.0'
        }
      })
    }
  );

  const apiKey = await response.json();

  // ⚠️ SAVE THIS KEY - IT WON'T BE SHOWN AGAIN!
  console.log('Your API Key:', apiKey.key);

  // Store in environment variables
  process.env.WISEYIELD_API_KEY = apiKey.key;
  ```

  ```javascript JavaScript (With Secure Storage) theme={null}
  async function createAndStoreApiKey(keyData) {
    try {
      const response = await fetch('/api/api-keys', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${clerkSessionToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(keyData)
      });

      if (!response.ok) {
        const error = await response.json();

        if (response.status === 400) {
          if (error.message?.includes('Maximum of 10')) {
            toast.error('You have reached the maximum of 10 API keys. Please revoke unused keys.');
          } else if (error.details?.invalidScopes) {
            toast.error(`Invalid scopes: ${error.details.invalidScopes.join(', ')}`);
          } else {
            toast.error(error.message || 'Validation failed');
          }
          return;
        }

        if (response.status === 401) {
          router.push('/sign-in');
          return;
        }

        toast.error('Failed to create API key');
        return;
      }

      const newKey = await response.json();

      // ⚠️ CRITICAL: Show key to user for copying
      await showApiKeyModal({
        key: newKey.key,
        name: newKey.name,
        message: '⚠️ Save this key now! It will never be shown again.'
      });

      toast.success('API key created successfully');

      return newKey;
    } catch (error) {
      toast.error('Network error. Please try again.');
    }
  }
  ```

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

  # Create key that expires in 90 days
  expires_at = (datetime.utcnow() + timedelta(days=90)).isoformat()

  response = requests.post(
      'https://www.wiseyield.co/api/api-keys',
      headers={
          'Authorization': 'Bearer {CLERK_SESSION_TOKEN}',
          'Content-Type': 'application/json'
      },
      json={
          'name': 'Production API Key',
          'environment': 'live',
          'scopes': ['farms:read', 'crops:read', 'analytics:read'],
          'rateLimit': 2000,
          'expiresAt': expires_at
      }
  )

  result = response.json()

  if response.status_code == 201:
      # ⚠️ SAVE THIS KEY IMMEDIATELY!
      api_key = result['key']
      print(f"Your API Key: {api_key}")
      print("Save this key securely - it won't be shown again!")

      # Store in environment file (example)
      with open('.env', 'a') as f:
          f.write(f'\nWISEYIELD_API_KEY={api_key}\n')
  else:
      print(f"Error: {result.get('error')}")
      if 'details' in result:
          print(f"Details: {result['details']}")
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "id": "key_abc123xyz789def456",
    "key": "wy_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2",
    "keyPrefix": "wy_live_a1b2c3d4",
    "keyPreview": "wy_live_a1b2c3d4...****",
    "name": "Production API Key",
    "environment": "live",
    "scopes": ["farms:read", "farms:write", "crops:read", "crops:write"],
    "rateLimit": 5000,
    "isActive": true,
    "usageCount": 0,
    "lastUsedAt": null,
    "createdAt": "2025-10-30T10:00:00.000Z",
    "updatedAt": "2025-10-30T10:00:00.000Z",
    "expiresAt": null,
    "metadata": {
      "application": "web-dashboard",
      "version": "1.0.0",
      "userAgent": "Mozilla/5.0..."
    }
  }
  ```

  ```json 400 Bad Request (Missing Name) theme={null}
  {
    "error": "Validation failed",
    "message": "Name and at least one scope are required"
  }
  ```

  ```json 400 Bad Request (Invalid Scopes) theme={null}
  {
    "error": "Validation failed",
    "message": "Invalid scopes provided",
    "details": {
      "invalidScopes": ["invalid:scope"],
      "validScopes": [
        "farms:read", "farms:write",
        "crops:read", "crops:write",
        "fields:read", "fields:write",
        "analytics:read",
        "tasks:read", "tasks:write",
        "team:read", "team:write",
        "webhooks:read", "webhooks:write",
        "all"
      ]
    }
  }
  ```

  ```json 400 Bad Request (Key Limit Exceeded) theme={null}
  {
    "error": "Limit exceeded",
    "message": "Maximum of 10 active API keys per user"
  }
  ```

  ```json 400 Bad Request (Invalid Environment) theme={null}
  {
    "error": "Validation failed",
    "message": "Environment must be \"live\" or \"test\""
  }
  ```

  ```json 400 Bad Request (Invalid Rate Limit) theme={null}
  {
    "error": "Validation failed",
    "message": "Rate limit must be between 1 and 100,000"
  }
  ```

  ```json 400 Bad Request (Invalid Expiry) theme={null}
  {
    "error": "Validation failed",
    "message": "Expiry date must be in the future"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "error": "Unauthorized",
    "message": "Authentication required"
  }
  ```

  ```json 403 Tier Insufficient theme={null}
  {
    "error": "Forbidden",
    "message": "API access requires the Summit tier. Upgrade at /dashboard/billing.",
    "code": "TIER_INSUFFICIENT",
    "details": { "currentTier": "professional", "requiredTier": "summit" }
  }
  ```

  ```json 500 Internal Server Error theme={null}
  {
    "error": "Internal server error"
  }
  ```
</ResponseExample>

## Errors

<ResponseField name="400" type="error">
  Bad Request - Validation failed (missing fields, invalid scopes, limit exceeded)
</ResponseField>

<ResponseField name="401" type="error">
  Unauthorized - User not signed in
</ResponseField>

<ResponseField name="500" type="error">
  Internal Server Error - Failed to create API key
</ResponseField>

## How It Works

**Creation Flow**:

1. **Authentication**: Verifies user is signed in
2. **Validation**: Checks all required fields and constraints:
   * Name and scopes are required
   * Scopes must be from valid list
   * Environment must be "live" or "test"
   * Rate limit: 1-100,000
   * Expiry date must be in future
3. **Limit Check**: Ensures user has \< 10 active keys
4. **Key Generation**:
   * Generates 24 random bytes (48 hex chars)
   * Formats as `wy_{environment}_{random}`
   * Creates SHA-256 hash for storage
   * Extracts first 16 chars as prefix
5. **Database Insert**: Saves key with metadata
6. **Audit Log**: Records creation for security tracking
7. **Response**: Returns full key (**only time it's shown**)

**Security**:

* Only SHA-256 hash stored in database
* Full key never retrievable after creation
* Prefix stored for identification
* User-agent automatically captured in metadata

## API Key Structure

### Key Format

```
wy_{environment}_{48_hexadecimal_characters}
```

**Example (Live)**:

```
wy_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2
```

**Example (Test)**:

```
wy_test_x9y8z7w6v5u4t3s2r1q0p9o8n7m6l5k4j3i2h1g0f9e8
```

**Components**:

* `wy_` - WiseYield identifier
* `{environment}` - `live` or `test`
* `{random}` - 48 hexadecimal characters (cryptographically secure)

### Storage

**Database**:

* `keyHash` - SHA-256 hash of full key
* `keyPrefix` - First 16 characters (for display/identification)

**Never Stored**:

* Full API key value (security best practice)

## Scopes Reference

### Farm Management

<ParamField name="farms:read" type="scope">
  View farms and farm details
</ParamField>

<ParamField name="farms:write" type="scope">
  Create, update, and delete farms
</ParamField>

### Crop Management

<ParamField name="crops:read" type="scope">
  View crops and crop details
</ParamField>

<ParamField name="crops:write" type="scope">
  Create, update, and delete crops
</ParamField>

### Field Management

<ParamField name="fields:read" type="scope">
  View fields and field analytics
</ParamField>

<ParamField name="fields:write" type="scope">
  Create, update, and delete fields
</ParamField>

### Analytics

<ParamField name="analytics:read" type="scope">
  Access analytics, predictions, and insights
</ParamField>

### Task Management

<ParamField name="tasks:read" type="scope">
  View tasks and schedules
</ParamField>

<ParamField name="tasks:write" type="scope">
  Create, update, and delete tasks
</ParamField>

### Team Management

<ParamField name="team:read" type="scope">
  View team members and invitations
</ParamField>

<ParamField name="team:write" type="scope">
  Invite, update, and remove team members
</ParamField>

### Webhooks

<ParamField name="webhooks:read" type="scope">
  View webhook configurations
</ParamField>

<ParamField name="webhooks:write" type="scope">
  Create, update, and delete webhooks
</ParamField>

### Full Access

<ParamField name="all" type="scope">
  **Full access to all resources and operations**

  ⚠️ Use with caution. Grants unrestricted access.
</ParamField>

## Use Cases

### Production Key with Limited Scopes

```javascript theme={null}
async function createProductionKey() {
  const response = await fetch('/api/api-keys', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkSessionToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Production Server',
      environment: 'live',
      scopes: [
        'farms:read',
        'crops:read',
        'fields:read',
        'analytics:read'
      ], // Read-only access
      rateLimit: 10000,
      metadata: {
        server: 'prod-api-01',
        region: 'us-east-1'
      }
    })
  });

  const key = await response.json();

  // ⚠️ Save to secure storage
  await storeInVault('WISEYIELD_API_KEY', key.key);

  return key;
}
```

### Test Key with Full Access

```javascript theme={null}
async function createTestKey() {
  const response = await fetch('/api/api-keys', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkSessionToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Local Development',
      environment: 'test',
      scopes: ['all'], // Full access for testing
      rateLimit: 1000
    })
  });

  const key = await response.json();

  // Can safely add to .env.local for development
  console.log('Add to .env.local:');
  console.log(`WISEYIELD_API_KEY=${key.key}`);

  return key;
}
```

### Temporary Key with Expiration

```javascript theme={null}
async function createTemporaryKey(daysUntilExpiry = 30) {
  const expiresAt = new Date();
  expiresAt.setDate(expiresAt.getDate() + daysUntilExpiry);

  const response = await fetch('/api/api-keys', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkSessionToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: `Temporary Access (${daysUntilExpiry}d)`,
      environment: 'live',
      scopes: ['farms:read', 'crops:read'],
      expiresAt: expiresAt.toISOString(),
      rateLimit: 500,
      metadata: {
        purpose: 'temporary-integration',
        requestedBy: 'partner-xyz'
      }
    })
  });

  const key = await response.json();

  // Share with partner
  console.log('Temporary key created');
  console.log(`Expires: ${new Date(key.expiresAt).toLocaleDateString()}`);

  return key;
}
```

### Mobile App Key

```javascript theme={null}
async function createMobileAppKey() {
  const response = await fetch('/api/api-keys', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${clerkSessionToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'iOS App v2.1',
      environment: 'live',
      scopes: [
        'farms:read',
        'crops:read',
        'crops:write',
        'tasks:read',
        'tasks:write'
      ],
      rateLimit: 2000,
      metadata: {
        platform: 'ios',
        appVersion: '2.1.0',
        buildNumber: '142'
      }
    })
  });

  const key = await response.json();

  // Embed in app configuration
  return key;
}
```

## Best Practices

<Tip>
  **Save Immediately**: Copy the full key to secure storage as soon as it's created. It cannot be retrieved later.
</Tip>

<Tip>
  **Principle of Least Privilege**: Grant only the scopes needed for the specific use case.
</Tip>

<Tip>
  **Use Test Keys for Development**: Create `test` environment keys for local development and testing.
</Tip>

<Tip>
  **Set Expiration**: For temporary access, always set an `expiresAt` date.
</Tip>

<Tip>
  **Descriptive Names**: Use clear, descriptive names that indicate purpose and environment.
</Tip>

<Tip>
  **Metadata for Tracking**: Use metadata to track which application, server, or user is using each key.
</Tip>

<Warning>
  **One-Time Display**: The full API key is shown ONLY once. Save it immediately or lose it forever.
</Warning>

<Warning>
  **10 Key Limit**: Maximum 10 active API keys per user. Revoke unused keys before creating new ones.
</Warning>

<Warning>
  **Never Commit Keys**: Never commit API keys to version control. Use environment variables.
</Warning>

<Warning>
  **Rotate Regularly**: Create new keys and revoke old ones periodically for security.
</Warning>

## Security Best Practices

**Key Storage**:

```bash theme={null}
# ✅ GOOD: Environment variables
WISEYIELD_API_KEY=wy_live_abc123...

# ✅ GOOD: Secret management services
aws secretsmanager create-secret --name wiseyield-api-key

# ❌ BAD: Hardcoded in code
const apiKey = "wy_live_abc123..."

# ❌ BAD: Committed to git
git add .env  # If .env contains keys!
```

**Key Rotation**:

```javascript theme={null}
async function rotateApiKey(oldKeyId) {
  // 1. Create new key
  const newKey = await createApiKey({
    name: 'Production Server (Rotated)',
    environment: 'live',
    scopes: [...],
    rateLimit: 10000
  });

  // 2. Update application config
  await updateEnvVar('WISEYIELD_API_KEY', newKey.key);

  // 3. Test new key
  await testApiKey(newKey.key);

  // 4. Revoke old key
  await revokeApiKey(oldKeyId);
}
```

**Scope Management**:

```javascript theme={null}
// ✅ GOOD: Minimal scopes
const readOnlyKey = {
  scopes: ['farms:read', 'crops:read']
};

// ⚠️ CAUTION: Broad access
const writeKey = {
  scopes: ['farms:write', 'crops:write', 'fields:write']
};

// ⚠️ DANGER: Full access
const adminKey = {
  scopes: ['all'] // Only for trusted applications
};
```

## Integration Examples

### API Key Creation Modal

```javascript theme={null}
function CreateApiKeyModal({ isOpen, onClose, onCreated }) {
  const [formData, setFormData] = useState({
    name: '',
    environment: 'live',
    scopes: [],
    rateLimit: 1000,
    expiresAt: '',
  });
  const [creating, setCreating] = useState(false);
  const [createdKey, setCreatedKey] = useState(null);

  async function handleCreate() {
    setCreating(true);

    try {
      const response = await fetch('/api/api-keys', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${clerkSessionToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          ...formData,
          expiresAt: formData.expiresAt || undefined
        })
      });

      if (!response.ok) {
        const error = await response.json();
        toast.error(error.message || 'Failed to create API key');
        return;
      }

      const newKey = await response.json();

      setCreatedKey(newKey);
      toast.success('API key created');
      onCreated?.(newKey);
    } finally {
      setCreating(false);
    }
  }

  if (createdKey) {
    return (
      <Modal open={isOpen} onClose={onClose}>
        <ModalTitle>API Key Created</ModalTitle>

        <ModalContent>
          <Alert severity="warning">
            <AlertTitle>Save This Key Now!</AlertTitle>
            <p>This is the only time you'll see the full key. Copy it to a secure location.</p>
          </Alert>

          <div className="my-4">
            <label className="block text-sm font-medium mb-2">API Key</label>
            <div className="flex gap-2">
              <code className="flex-1 p-3 bg-gray-100 rounded font-mono text-sm break-all">
                {createdKey.key}
              </code>
              <button
                onClick={() => {
                  navigator.clipboard.writeText(createdKey.key);
                  toast.success('Copied to clipboard');
                }}
                className="btn btn-secondary"
              >
                Copy
              </button>
            </div>
          </div>

          <div className="text-sm text-gray-600">
            <div><strong>Name:</strong> {createdKey.name}</div>
            <div><strong>Environment:</strong> {createdKey.environment}</div>
            <div><strong>Rate Limit:</strong> {createdKey.rateLimit}/hour</div>
          </div>
        </ModalContent>

        <ModalActions>
          <button onClick={onClose} className="btn btn-primary">
            I've Saved My Key
          </button>
        </ModalActions>
      </Modal>
    );
  }

  return (
    <Modal open={isOpen} onClose={onClose}>
      <ModalTitle>Create API Key</ModalTitle>

      <ModalContent>
        <form className="space-y-4">
          <div>
            <label className="block text-sm font-medium mb-2">Name *</label>
            <input
              type="text"
              value={formData.name}
              onChange={(e) => setFormData({ ...formData, name: e.target.value })}
              placeholder="e.g., Production Server"
              required
            />
          </div>

          <div>
            <label className="block text-sm font-medium mb-2">Environment</label>
            <select
              value={formData.environment}
              onChange={(e) => setFormData({ ...formData, environment: e.target.value })}
            >
              <option value="live">Live (Production)</option>
              <option value="test">Test (Development)</option>
            </select>
          </div>

          <div>
            <label className="block text-sm font-medium mb-2">Scopes *</label>
            <ScopeSelector
              selected={formData.scopes}
              onChange={(scopes) => setFormData({ ...formData, scopes })}
            />
          </div>

          <div>
            <label className="block text-sm font-medium mb-2">
              Rate Limit (requests/hour)
            </label>
            <input
              type="number"
              value={formData.rateLimit}
              onChange={(e) => setFormData({ ...formData, rateLimit: parseInt(e.target.value) })}
              min="1"
              max="100000"
            />
          </div>

          <div>
            <label className="block text-sm font-medium mb-2">
              Expiration Date (optional)
            </label>
            <input
              type="datetime-local"
              value={formData.expiresAt}
              onChange={(e) => setFormData({ ...formData, expiresAt: e.target.value })}
            />
          </div>
        </form>
      </ModalContent>

      <ModalActions>
        <button onClick={onClose} disabled={creating}>
          Cancel
        </button>
        <button
          onClick={handleCreate}
          disabled={!formData.name || formData.scopes.length === 0 || creating}
          className="btn btn-primary"
        >
          {creating ? 'Creating...' : 'Create API Key'}
        </button>
      </ModalActions>
    </Modal>
  );
}
```

## Related Endpoints

* [List API Keys](/api-reference/api-keys/list-api-keys) - GET to list all API keys
* [Revoke API Key](/api-reference/api-keys/revoke-api-key) - DELETE to revoke existing key
