# Authenticating with a Nylas Service Account

Source: https://developer.nylas.com/docs/v3/auth/nylas-service-account/

> **Info:** 
> **The Nylas Service Account is in beta.** The authentication mechanism and features may change before general availability.

> **Warn:** 
> **Nylas Service Accounts are different from Google and Microsoft "Service Accounts"**. Google and Microsoft Service Accounts are used for [bulk authentication grants](/docs/v3/auth/bulk-auth-grants/). Nylas Service Accounts are an organization-level authentication mechanism that uses cryptographic request signing to access admin APIs like the [Manage Domains API](/docs/v3/email/domains/).

A Nylas Service Account authenticates your requests to organization-level Nylas admin APIs. Unlike API keys and access tokens, Nylas Service Account authentication uses RSA cryptographic request signing. Each request includes a signature generated from your private key, which Nylas verifies server-side.

Currently, Nylas Service Account authentication is used by:

- The [Manage Domains API](/docs/v3/email/domains/)
- The [Manage API Keys API](/docs/reference/api/manage-api-keys/)

## Before you begin

To get a Nylas Service Account, contact [Nylas Support](mailto:support@nylas.com). They will provide you with a credentials JSON file.

## Credential fields

Your Nylas Service Account credentials file contains the following fields:

| Field             | Description                                                                         |
| ----------------- | ----------------------------------------------------------------------------------- |
| `name`            | A human-readable name for the service account.                                      |
| `type`            | The credential type. Always `"service_account"`.                                    |
| `private_key_id`  | The unique identifier for your private key. Used as the `X-Nylas-Kid` header value. |
| `private_key`     | Your RSA private key in PEM format. Used to sign requests.                          |
| `organization_id` | The ID of your Nylas organization.                                                  |
| `region`          | The Nylas data center region (`us` or `eu`).                                        |

> **Warn:** 
> **Store your credentials file securely.** The private key grants access to organization-level admin APIs. Never commit it to version control or expose it in client-side code. Use a secrets manager or environment variables.

## Required headers

Every request authenticated with a Nylas Service Account must include these four custom headers:

| Header              | Description                                                                                                                             |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Nylas-Kid`       | The `private_key_id` from your credentials file. Identifies which key Nylas should use to verify the signature.                         |
| `X-Nylas-Timestamp` | The current time in seconds as a Unix timestamp. Must be within 5 minutes of the server time.                                           |
| `X-Nylas-Nonce`     | A unique, randomly generated string (minimum 16 characters) for each request. Nylas rejects duplicate nonces to prevent replay attacks. |
| `X-Nylas-Signature` | A Base64-encoded RSA-SHA256 signature of the request. See [Generating the signature](#generating-the-signature) for details.            |

## Generating the signature

To generate the `X-Nylas-Signature` header value, follow these steps:

1. **Build the canonical data object** containing:
   - `path` — the API endpoint path (for example, `/v3/admin/domains`)
   - `method` — the HTTP method in **lowercase** (for example, `get`, `post`)
   - `timestamp` — the same Unix timestamp used in `X-Nylas-Timestamp`
   - `nonce` — the same nonce used in `X-Nylas-Nonce`
   - `payload` — (only for POST/PUT/PATCH) the canonical JSON request body as a string. Omit this field for GET and DELETE requests.

2. **Serialize to canonical JSON.** The keys must be **sorted alphabetically** with no extra whitespace.

3. **Hash the canonical JSON.** Compute a SHA-256 digest of the serialized string.

4. **Sign the hash.** Sign the SHA-256 digest using RSA PKCS#1 v1.5 with your private key (2048-bit minimum).

5. **Encode the signature.** Base64-encode the resulting signature bytes.

> **Info:** 
> **Keep your system clock synchronized.** Nylas rejects requests where the `X-Nylas-Timestamp` header differs from the server time by more than 5 minutes. Use NTP to keep your clock accurate.

## Reference implementation

The following Go program generates signed curl commands for testing the Manage Domains API. For production use, integrate the signing logic directly into your application.

```go
package main


  "crypto"
  "crypto/rand"
  "crypto/rsa"
  "crypto/sha256"
  "crypto/x509"
  "encoding/base64"
  "encoding/json"
  "encoding/pem"
  "fmt"
  "os"
  "sort"
  "strings"
  "time"
)

const nonceLength = 20

// generateNonce creates a cryptographically
// secure random string.
func generateNonce() (string, error) {
  const chars = "abcdefghijklmnopqrstuvwxyz" +
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  result := make([]byte, nonceLength)
  randomBytes := make([]byte, nonceLength)
  if _, err := rand.Read(randomBytes); err != nil {
    return "", fmt.Errorf(
      "failed to generate random bytes: %w", err)
  }
  for i := 0; i < nonceLength; i++ {
    result[i] = chars[randomBytes[i]%byte(len(chars))]
  }
  return string(result), nil
}

// canonicalJSON produces a deterministic JSON
// representation with sorted keys.
func canonicalJSON(
  data map[string]interface{},
) ([]byte, error) {
  keys := make([]string, 0, len(data))
  for k := range data {
    keys = append(keys, k)
  }
  sort.Strings(keys)

  var sb strings.Builder
  sb.WriteString("{")
  for i, k := range keys {
    if i > 0 {
      sb.WriteString(",")
    }
    keyJSON, err := json.Marshal(k)
    if err != nil {
      return nil, fmt.Errorf(
        "failed to marshal key %s: %w", k, err)
    }
    sb.Write(keyJSON)
    sb.WriteString(":")
    valueJSON, err := json.Marshal(data[k])
    if err != nil {
      return nil, fmt.Errorf(
        "failed to marshal value for key %s: %w",
        k, err)
    }
    sb.Write(valueJSON)
  }
  sb.WriteString("}")
  return []byte(sb.String()), nil
}

// loadPrivateKey decodes and parses a
// base64-encoded PEM private key.
func loadPrivateKey(
  base64Key string,
) (*rsa.PrivateKey, error) {
  pemBytes, err := base64.StdEncoding.DecodeString(
    base64Key)
  if err != nil {
    return nil, fmt.Errorf(
      "failed to decode base64: %w", err)
  }

  block, _ := pem.Decode(pemBytes)
  if block == nil {
    return nil, fmt.Errorf("failed to parse PEM block")
  }

  // Try PKCS#1 format first.
  if key, err := x509.ParsePKCS1PrivateKey(
    block.Bytes); err == nil {
    return key, nil
  }

  // Try PKCS#8 format.
  keyInterface, err := x509.ParsePKCS8PrivateKey(
    block.Bytes)
  if err != nil {
    return nil, fmt.Errorf(
      "failed to parse private key "+
        "(tried PKCS#1 and PKCS#8): %w", err)
  }
  rsaKey, ok := keyInterface.(*rsa.PrivateKey)
  if !ok {
    return nil, fmt.Errorf("private key is not RSA")
  }
  return rsaKey, nil
}

// signRequest creates the signature for a
// Nylas API request.
func signRequest(
  privateKey *rsa.PrivateKey,
  path, method string,
  timestamp int64,
  nonce string,
  payload []byte,
) (string, error) {
  canonicalData := map[string]interface{}{
    "method":    method,
    "nonce":     nonce,
    "path":      path,
    "timestamp": timestamp,
  }

  // Only include payload for POST/PUT/PATCH.
  if len(payload) > 0 && (method == "post" ||
    method == "put" || method == "patch") {
    canonicalData["payload"] = string(payload)
  }

  canonicalBytes, err := canonicalJSON(canonicalData)
  if err != nil {
    return "", fmt.Errorf(
      "failed to create canonical JSON: %w", err)
  }

  hashed := sha256.Sum256(canonicalBytes)
  signature, err := rsa.SignPKCS1v15(
    rand.Reader, privateKey,
    crypto.SHA256, hashed[:])
  if err != nil {
    return "", fmt.Errorf("failed to sign: %w", err)
  }
  return base64.StdEncoding.EncodeToString(
    signature), nil
}

func main() {
  // Required environment variables.
  privateKeyBase64 := os.Getenv("PRIVATE_KEY_B64")
  path := os.Getenv("REQUEST_PATH")
  kid := os.Getenv("NYLAS_KID")

  if privateKeyBase64 == "" || path == "" ||
    kid == "" {
    fmt.Fprintln(os.Stderr,
      "Error: PRIVATE_KEY_B64, REQUEST_PATH, "+
        "and NYLAS_KID are required")
    os.Exit(1)
  }

  // Optional environment variables.
  method := strings.ToLower(
    os.Getenv("REQUEST_METHOD"))
  if method == "" {
    method = "get"
  }
  payloadStr := os.Getenv("REQUEST_PAYLOAD")
  baseURL := os.Getenv("BASE_URL")
  if baseURL == "" {
    baseURL = "https://api.us.nylas.com"
  }

  privateKey, err := loadPrivateKey(privateKeyBase64)
  if err != nil {
    fmt.Fprintf(os.Stderr,
      "Error loading private key: %v\n", err)
    os.Exit(1)
  }

  timestamp := time.Now().Unix()
  nonce, err := generateNonce()
  if err != nil {
    fmt.Fprintf(os.Stderr,
      "Error generating nonce: %v\n", err)
    os.Exit(1)
  }

  // Canonicalize the payload if provided.
  var payloadBytes []byte
  if payloadStr != "" {
    var payloadMap map[string]interface{}
    if err := json.Unmarshal(
      []byte(payloadStr), &payloadMap); err != nil {
      fmt.Fprintf(os.Stderr,
        "Error parsing REQUEST_PAYLOAD: %v\n", err)
      os.Exit(1)
    }
    payloadBytes, err = canonicalJSON(payloadMap)
    if err != nil {
      fmt.Fprintf(os.Stderr,
        "Error creating canonical payload: %v\n", err)
      os.Exit(1)
    }
  }

  signature, err := signRequest(
    privateKey, path, method,
    timestamp, nonce, payloadBytes)
  if err != nil {
    fmt.Fprintf(os.Stderr,
      "Error signing request: %v\n", err)
    os.Exit(1)
  }

  // Print the signed curl command.
  fmt.Printf(
    "curl --location --request %s '%s%s' \\\n",
    strings.ToUpper(method), baseURL, path)
  fmt.Printf(
    " --header 'X-Nylas-Kid: %s' \\\n", kid)
  fmt.Printf(
    " --header 'X-Nylas-Nonce: %s' \\\n", nonce)
  fmt.Printf(
    " --header 'X-Nylas-Timestamp: %d' \\\n",
    timestamp)
  fmt.Printf(
    " --header 'X-Nylas-Signature: %s' \\\n",
    signature)
  fmt.Printf(
    " --header 'Content-Type: application/json'")
  if payloadStr != "" {
    fmt.Printf(" \\\n --data '%s'", payloadStr)
  }
  fmt.Println()
}
```

**Usage:** Set the required environment variables and run the program.

```bash [serviceAccountAuth-GET example]
PRIVATE_KEY_B64="$(cat private_key_b64.txt)" \
REQUEST_PATH="/v3/admin/domains" \
NYLAS_KID="08d8bdae-9981-432c-ac66-3006919d908e" \
go run domain.go
```

```bash [serviceAccountAuth-POST example]
PRIVATE_KEY_B64="$(cat private_key_b64.txt)" \
REQUEST_PATH="/v3/admin/domains/<DOMAIN_ID>/info" \
REQUEST_METHOD="post" \
REQUEST_PAYLOAD='{"type":"ownership"}' \
NYLAS_KID="08d8bdae-9981-432c-ac66-3006919d908e" \
go run domain.go
```

## Related resources

- [Managing domains](/docs/v3/email/domains/) — register and verify email domains using a Nylas Service Account
- [Manage Domains API reference](/docs/reference/api/manage-domains/) — full API reference for domain endpoints
- [Manage API Keys API reference](/docs/reference/api/manage-api-keys/) — full API reference for API key endpoints