Skip to main content
POST
/
api
/
api-keys
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"
    }
  }'
{
  "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..."
  }
}

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.

Endpoint

POST https://www.wiseyield.co/api/api-keys
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 and use Settings → API Keys → Create API Key.
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 for what’s included at each tier.

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

name
string
required
User-friendly name for the API key (e.g., “Production Server”, “Mobile App”)
scopes
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)
environment
string
default:"live"
Environment type: live (production) or test (development)
rateLimit
number
default:"1000"
Maximum API requests per hour (1-100,000)
expiresAt
string
ISO 8601 datetime when key should expire (optional, no expiration if omitted)
metadata
object
Custom metadata to attach to the key (optional)

Response

Returns the newly created API key object with the full key.
id
string
Unique identifier (format: key_{nanoid})
key
string
⚠️ CRITICAL: Full API key (shown ONLY ONCE, never retrievable again)Format: wy_{environment}_{48_hex_characters}
keyPrefix
string
First 16 characters for identification
keyPreview
string
Masked version for display
name
string
Name provided in request
environment
string
Environment: live or test
scopes
array
Array of granted permission scopes
rateLimit
number
Requests per hour limit
isActive
boolean
Always true on creation
usageCount
number
Always 0 on creation
lastUsedAt
string
Always null on creation
createdAt
string
ISO 8601 timestamp of creation
updatedAt
string
ISO 8601 timestamp of last update
expiresAt
string
Expiration timestamp (null if no expiration)
metadata
object
Custom metadata (includes userAgent automatically)
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"
    }
  }'
{
  "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..."
  }
}

Errors

400
error
Bad Request - Validation failed (missing fields, invalid scopes, limit exceeded)
401
error
Unauthorized - User not signed in
500
error
Internal Server Error - Failed to create API key

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

Crop Management

Field Management

Analytics

Task Management

Team Management

Webhooks

Full Access

Use Cases

Production Key with Limited Scopes

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

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

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

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

Save Immediately: Copy the full key to secure storage as soon as it’s created. It cannot be retrieved later.
Principle of Least Privilege: Grant only the scopes needed for the specific use case.
Use Test Keys for Development: Create test environment keys for local development and testing.
Set Expiration: For temporary access, always set an expiresAt date.
Descriptive Names: Use clear, descriptive names that indicate purpose and environment.
Metadata for Tracking: Use metadata to track which application, server, or user is using each key.
One-Time Display: The full API key is shown ONLY once. Save it immediately or lose it forever.
10 Key Limit: Maximum 10 active API keys per user. Revoke unused keys before creating new ones.
Never Commit Keys: Never commit API keys to version control. Use environment variables.
Rotate Regularly: Create new keys and revoke old ones periodically for security.

Security Best Practices

Key Storage:
# ✅ 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:
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:
// ✅ 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

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>
  );
}