# Create IAM credential

> **POST** `https://api.us.nylas.com/v3/iam/principals/{principal_id}/credentials`

Source: https://developer.nylas.com/docs/reference/api/iam-credentials/create-iam-credential/

Requires `iam.credentials.create` on an active principal bound to the authenticated organization. Authenticate with an active, unexpired IAM credential of type `api_key`. Application API keys (including legacy API keys) and provider OAuth tokens cannot authorize IAM management endpoints. Use an existing IAM credential with these permissions, or [create your first management credential in the Dashboard](/docs/v3/auth/nylas-iam/manage-principals-with-api/#create-a-management-credential-in-the-dashboard).

Creates an api_key credential for an active principal. name and expires_at are required. expires_at is an integer Unix timestamp in seconds, measured from January 1, 1970 at 00:00:00 UTC. It must be after the current time and no more than 10 calendar years ahead. Store the secret securely: only this response includes it. Unknown fields and null values are rejected.

**Authentication:** IAM_API_KEY

## Parameters

### Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `principal_id` | string | Yes | ID of the principal in the authenticated organization. |

## Request body

Content-Type: application/json

- `name` (string, minLength: 1, maxLength: 128) **(required)** - Descriptive workload name. Surrounding whitespace is removed.
- `status` (string, one of: `active`, `disabled`, default: `"active"`) - Whether the principal or credential can authenticate.
- `expires_at` (integer, format: int64) **(required)** - Required expiration as an integer number of seconds since the Unix epoch (January 1, 1970 at 00:00:00 UTC). For example, `1799193600` represents January 6, 2027 at 00:00:00 UTC. Use seconds, not milliseconds, an ISO 8601 string, or a duration. In JavaScript, use `Math.floor(date.getTime() / 1000)`; in Python, use `int(utc_datetime.timestamp())` with a timezone-aware UTC datetime. The value must be after the current time and no later than 10 calendar years from now. You can't change it after creation; create a replacement credential to use a different expiration.

## Responses

### 200 - OK

- `request_id` (string) - Request ID for troubleshooting.
- `data` (object)
  - `id` (string) **(required)** - Opaque credential ID.
  - `principal_id` (string) **(required)** - ID of the principal that owns this credential.
  - `type` (string, one of: `api_key`) **(required)** - Supported IAM credential type.
  - `name` (string, minLength: 1, maxLength: 128) **(required)** - Descriptive workload name. Surrounding whitespace is removed.
  - `status` (string, one of: `active`, `disabled`) **(required)** - Whether the principal or credential can authenticate.
  - `expires_at` (integer, nullable, format: int64) **(required)** - Expiration as an integer number of seconds since the Unix epoch (January 1, 1970 at 00:00:00 UTC). For example, `1799193600` represents January 6, 2027 at 00:00:00 UTC. Newly created credentials always have an expiration.
  - `created_at` (integer, format: int64) **(required)** - Unix timestamp in seconds.
  - `updated_at` (integer, format: int64) **(required)** - Unix timestamp in seconds.
  - `secret` (string) **(required)** - IAM API key secret. Returned only once, on creation. Store securely; never log this response.

### 400 - Invalid request or object quota exceeded

- `request_id` (string) - Request ID for troubleshooting.
- `error` (object)
  - `code` (string) - Error code.
  - `type` (string) **(required)** - Error category.
  - `message` (string) **(required)** - Description of the failure.
  - `details` (object) - Additional error context. Quota errors include resource_type, limit, and current.
    - `resource_type` (string)
    - `limit` (integer)
    - `current` (integer)

### 401 - Invalid, disabled, deleted, or expired authentication credential

- `request_id` (string) - Request ID for troubleshooting.
- `error` (object)
  - `code` (string) - Error code.
  - `type` (string) **(required)** - Error category.
  - `message` (string) **(required)** - Description of the failure.
  - `details` (object) - Additional error context. Quota errors include resource_type, limit, and current.
    - `resource_type` (string)
    - `limit` (integer)
    - `current` (integer)

### 403 - Missing management permission or organization binding

- `request_id` (string) - Request ID for troubleshooting.
- `error` (object)
  - `code` (string) - Error code.
  - `type` (string) **(required)** - Error category.
  - `message` (string) **(required)** - Description of the failure.
  - `details` (object) - Additional error context. Quota errors include resource_type, limit, and current.
    - `resource_type` (string)
    - `limit` (integer)
    - `current` (integer)

### 404 - Principal or credential not found in this organization

- `request_id` (string) - Request ID for troubleshooting.
- `error` (object)
  - `code` (string) - Error code.
  - `type` (string) **(required)** - Error category.
  - `message` (string) **(required)** - Description of the failure.
  - `details` (object) - Additional error context. Quota errors include resource_type, limit, and current.
    - `resource_type` (string)
    - `limit` (integer)
    - `current` (integer)

### 409 - Conflicting principal or credential state

- `request_id` (string) - Request ID for troubleshooting.
- `error` (object)
  - `code` (string) - Error code.
  - `type` (string) **(required)** - Error category.
  - `message` (string) **(required)** - Description of the failure.
  - `details` (object) - Additional error context. Quota errors include resource_type, limit, and current.
    - `resource_type` (string)
    - `limit` (integer)
    - `current` (integer)

### 429 - Too many requests

- `request_id` (string) - Request ID for troubleshooting.
- `error` (object)
  - `code` (string) - Error code.
  - `type` (string) **(required)** - Error category.
  - `message` (string) **(required)** - Description of the failure.
  - `details` (object) - Additional error context. Quota errors include resource_type, limit, and current.
    - `resource_type` (string)
    - `limit` (integer)
    - `current` (integer)

## Code samples

### cURL

```bash
# Store the returned secret securely. Do not capture it in shared logs.
EXPIRES_AT=$(python3 -c "import time; print(int(time.time()) + 90 * 86400)")

curl --request POST \
  --url "https://api.us.nylas.com/v3/iam/principals/${NYLAS_PRINCIPAL_ID}/credentials" \
  --header "Authorization: Bearer $NYLAS_IAM_ADMIN_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"Support worker key\", \"expires_at\": ${EXPIRES_AT}}"

```

### Node.js (HTTP)

```javascript
const requiredEnvironment = (name) => {
  const value = process.env[name]?.trim();
  if (!value) throw new Error(`Set ${name} before running this example`);
  return value;
};
const adminKey = requiredEnvironment("NYLAS_IAM_ADMIN_KEY");
// Store data.secret in your secrets service. Never log the response.
const principalId = encodeURIComponent(
  requiredEnvironment("NYLAS_PRINCIPAL_ID"),
);
const response = await fetch(
  `https://api.us.nylas.com/v3/iam/principals/${principalId}/credentials`,
  {
    method: "POST",
    signal: AbortSignal.timeout(30000),
    headers: {
      Authorization: `Bearer ${adminKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Support worker key",
      expires_at: Math.floor(Date.now() / 1000) + 90 * 86400,
    }),
  },
);
if (!response.ok)
  throw new Error(`Credential creation failed (${response.status})`);
const { data: credential } = await response.json();
// Pass credential.secret directly to your secrets-service client here.
// Save credential.id so you can rotate or revoke this key later.

```

### Python (HTTP)

```python
import json
import os
import time
from urllib.parse import quote
from urllib.request import Request, urlopen

principal_id = quote(os.environ["NYLAS_PRINCIPAL_ID"], safe="")
request = Request(
    f"https://api.us.nylas.com/v3/iam/principals/{principal_id}/credentials",
    method="POST",
    headers={
        "Authorization": f"Bearer {os.environ['NYLAS_IAM_ADMIN_KEY']}",
        "Content-Type": "application/json",
    },
    data=json.dumps({
        "name": "Support worker key",
        "expires_at": int(time.time()) + 90 * 86400,
    }).encode(),
)
with urlopen(request, timeout=30) as response:
    credential = json.load(response)["data"]
# Pass credential["secret"] directly to your secrets-service client here.
# Save credential["id"] for rotation or revocation. Never log the response.

```
