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

# Authentication

> API Keys, OAuth tokens, and how to attach credentials to every CRM+ API request.

Every CRM+ API request must include a valid Bearer token in the `Authorization` header. The API supports two token types: **API Keys** for server-to-server integrations, and **OAuth tokens** for integrations that act on behalf of a specific Virtuous user.

If you have not yet generated a credential, the [Quickstart](/crm/quickstart) walks you through creating an API Key and verifying it works.

## Choose your authentication method

| Method          | Best for                                                                            | Token lifetime                                    |
| --------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------- |
| **API Key**     | Partner integrations, automated syncs, background jobs, service accounts            | 15 years (static)                                 |
| **OAuth Token** | User-delegated access — your integration acts on behalf of a specific Virtuous user | 15 days (access token) / 365 days (refresh token) |

For the majority of partner integrations, use an API Key. OAuth tokens are appropriate when your integration needs to perform actions as a specific Virtuous user — for example, an interactive admin tool where the user signs in with their own Virtuous credentials.

***

## API Keys

### Set up an API Key

<Steps>
  <Step title="Log into the Virtuous dashboard">
    Navigate to your organization's Virtuous instance and log in with an account that has administrator permissions. Only administrators can create API Keys.
  </Step>

  <Step title="Open API Keys settings">
    Go to  **Settings → All Settings → Connectivity → API Keys**.
  </Step>

  <Step title="Create a new key">
    Click **Create an API Key** button. Enter a descriptive name (typically the name of the integration you are building) and select the appropriate permission group. Click **Save**.
  </Step>

  <Step title="Copy the API key">
    After saving, the API key will be displayed and you can always access it by clicking on the edit icon for an API key.  The key is hidden for security purposes but you can click on the eye icon to show the key or the copy icon to copy it.
  </Step>

  <Step title="Use the key in requests">
    Pass the key as a Bearer token in the `Authorization` header on every request:

    ```text theme={null}
    Authorization: Bearer YOUR_API_TOKEN
    ```
  </Step>
</Steps>

<Warning>
  API Keys are static credentials with a 15-year lifetime. Treat them like passwords. Store them in a secrets manager or environment variable — never in source code, version control, or client-side JavaScript. If a key is compromised, revoke it from **Settings → Integrations → API Keys** and create a replacement.
</Warning>

### API Keys for partner integrations

A common partner question: **does each of our nonprofit customers need a separate API Key?** Yes. An API Key is generated inside a specific Virtuous organization by an administrator at that organization, and it grants access only to that organization's data. A partner integration that serves multiple nonprofit customers stores one API Key per customer and uses the correct key when calling the API on that customer's behalf.

See [Base URLs and Environments](/crm/base-urls) for guidance on storing and routing per-tenant credentials.

***

## OAuth Tokens

### Obtain an OAuth token

OAuth tokens are issued via a `POST` to `https://api.virtuoussoftware.com/Token` using the `password` grant type. URL-encode the email address and password before constructing the request body to handle special characters correctly.

<Warning>
  The `/Token` request body must be sent as `application/x-www-form-urlencoded`. Sending it as `form-data` (`multipart/form-data`), which some HTTP clients and the Postman collection use by default, will fail. If you cannot obtain a token, check that your client is set to `x-www-form-urlencoded` and that the `Content-Type` header matches.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.virtuoussoftware.com/Token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=password&username=YOUR_EMAIL&password=YOUR_PASSWORD"
  ```

  ```javascript JavaScript theme={null}
  const body = new URLSearchParams({
    grant_type: 'password',
    username: 'YOUR_EMAIL',
    password: 'YOUR_PASSWORD',
  });

  const response = await fetch('https://api.virtuoussoftware.com/Token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body,
  });

  const token = await response.json();
  ```
</CodeGroup>

A successful response returns the access token along with a refresh token:

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 3599,
  "refresh_token": "zyx987...",
  "userName": "developer@example.org",
  "twoFactorEnabled": "False",
  ".issued": "Thu, 10 Feb 2022 22:27:19 GMT",
  ".expires": "Sat, 25 Feb 2022 22:27:19 GMT"
}
```

Use the value of `access_token` as your Bearer token.

<Note>
  The `expires_in` value is in seconds and reflects the access token's session window — the example above shows roughly one hour. The OAuth token's full effective lifetime is 15 days; use the refresh token to obtain a new access token without re-prompting the user for credentials.
</Note>

### Refresh an OAuth token

When the access token expires, use the refresh token to obtain a new one without requiring the user to re-authenticate. Refresh tokens are valid for 365 days.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.virtuoussoftware.com/Token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN"
  ```

  ```javascript JavaScript theme={null}
  const body = new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: 'YOUR_REFRESH_TOKEN',
  });

  const response = await fetch('https://api.virtuoussoftware.com/Token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body,
  });

  const refreshed = await response.json();
  ```
</CodeGroup>

Store the new `access_token` and `refresh_token` from the response — Virtuous may rotate the refresh token on each refresh, so always replace both.

### Two-factor authentication

If the account has Two-Factor Authentication (2FA) enabled, the initial token request returns a `202 Accepted` response indicating that a verification code is required. The user receives the code by SMS.

Resubmit the request with the code added as an `otp` field:

```bash cURL theme={null}
curl -X POST https://api.virtuoussoftware.com/Token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&username=YOUR_EMAIL&password=YOUR_PASSWORD&otp=YOUR_OTP"
```

A successful response after OTP submission returns the standard token payload.

***

## Making an authenticated request

Pass your token in the `Authorization` header on every API request. The header format is identical for API Keys and OAuth tokens.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.virtuoussoftware.com/api/Contact/4821 \
    -H "Authorization: Bearer YOUR_API_TOKEN" \
    -H "Content-Type: application/json"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://api.virtuoussoftware.com/api/Contact/4821',
    {
      headers: {
        Authorization: `Bearer ${process.env.VIRTUOUS_API_TOKEN}`,
        'Content-Type': 'application/json',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  const contact = await response.json();
  ```
</CodeGroup>

For a longer walkthrough — request anatomy, response inspection, and debugging — see [Make Your First API Call](/crm/first-api-call).

***

## Authentication errors

| Scenario                              | Status | What it means                                                                                                     |
| ------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------- |
| Missing `Authorization` header        | `401`  | No credentials were provided on the request.                                                                      |
| Invalid or expired token              | `401`  | The token is malformed, has been revoked in the Virtuous UI, or — for OAuth — has expired.                        |
| Valid token, insufficient permissions | `403`  | The credential is valid but the permission group attached to it does not grant access to this resource or action. |

<Note>
  The CRM+ API does not currently return a structured JSON error body for all `401` responses — some endpoints return a plain-text `Authorization has been denied for this request.` message. The target canonical error shape is documented on the [Error Handling page](/crm/error-handling). Handle `401` defensively by checking `response.status` first; do not assume the body is always JSON.
</Note>

If you receive a `403`, the most common cause is that the API Key's permission group does not include the resource you are calling. Confirm the permission group assignment in **Settings → Integrations → API Keys** and adjust if needed.

***

## HMAC-authenticated endpoints

A small number of CRM+ endpoints require **HMAC authentication** instead of Bearer token authentication. The most notable is `GET /api/Contact/ByReference/{referenceId}`, which is used to look up Contacts by an external reference identifier.

HMAC auth uses a different credential and signing mechanism from the standard API Key. If you receive a `401` on an endpoint marked as "HMAC Auth only," contact Virtuous support to obtain HMAC credentials and signing instructions.

<Warning>
  The CRM+ spec flags HMAC-restricted endpoints with only the note "HMAC Auth only" — no inline explanation of how to obtain or use HMAC credentials. Until this documentation gap is addressed, contact Virtuous support before relying on HMAC-only endpoints in your integration.
</Warning>

***

## Security recommendations for partner integrations

* **Use a secrets manager.** Store every customer's API Key in a managed secrets service (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler, 1Password, etc.) — never in your application database in plaintext.
* **Scope per customer.** Use a separate API Key for each nonprofit customer. Do not pool credentials across customers.
* **Rotate on compromise.** If an API Key is exposed in logs, source control, or a third-party system, treat it as compromised. Revoke it in the Virtuous UI and generate a replacement immediately.
* **Audit your dependency chain.** If your integration calls third-party services with the Virtuous-derived data, confirm those services do not log or store the API Key inadvertently.
* **Prefer API Keys over OAuth for service accounts.** OAuth tokens are tied to a specific Virtuous user; if that user leaves the nonprofit, the OAuth credentials are revoked. API Keys are independent of any single user and survive personnel changes.

***

## Cross-API note

The [Raise API](/raise/authentication) and [Volunteer API](/volunteer/authentication) use the same `Authorization: Bearer` header format, but tokens are not interchangeable across products. Each product's authentication is independent — partner integrations spanning multiple Virtuous products store and route a separate credential for each.

***

## Next steps

<CardGroup cols={2}>
  <Card title="Base URLs and Environments" icon="server" href="/crm/base-urls">
    The base URL, why there is no sandbox environment, and how to scope credentials per customer.
  </Card>

  <Card title="Make Your First API Call" icon="terminal" href="/crm/first-api-call">
    A walkthrough of request anatomy, response inspection, and debugging your first call.
  </Card>

  <Card title="Error Handling" icon="circle-exclamation" href="/crm/error-handling">
    The error response shape and how to write defensive client code.
  </Card>

  <Card title="Rate Limits" icon="gauge" href="/crm/rate-limits">
    The 5,000-requests-per-hour limit and how to handle 429 responses gracefully.
  </Card>
</CardGroup>
