> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cloudraker.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cloudraker.com/_mcp/server.

# Authentication

Every authenticated request to the CloudRaker API carries a single header:

```
Authorization: Bearer <token>
```

The gateway accepts two kinds of token under that header and tells them apart automatically:

1. **Organization API keys** — a machine credential scoped to one organization. This is the developer path.
2. **Session JWTs** — the token the web app uses for a signed-in human. They carry that person's role and per-resource grants.

Both resolve to the same tenant-scoped identity, so both can call the same routes. One exception is covered under [Capability differences](#api-keys-vs-session-jwts) below.

## Organization API keys

An org API key is a long-lived credential that belongs to your organization, not to a person. Its actions are recorded under the key's name. Use it for server-to-server automation.

### Create a key

Create keys in the app under **Admin → API keys** (admin-only). The [API keys guide](/workspace/admin/api-keys) has the full UI walkthrough. The plaintext value is shown **once** at creation. You cannot retrieve it again.

You can also manage keys over the API (admin-gated):

### Request

GET [https://api.cloudraker.com/api-keys](https://api.cloudraker.com/api-keys)

```curl
curl https://api.cloudraker.com/api-keys \
     -H "Authorization: Bearer <token>"
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.organizationSettings.listApiKeys();
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.organization_settings.list_api_keys()

```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/api-keys"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/api-keys")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.cloudraker.com/api-keys")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.cloudraker.com/api-keys', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/api-keys");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/api-keys")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Request

POST [https://api.cloudraker.com/api-keys](https://api.cloudraker.com/api-keys)

```curl
curl -X POST https://api.cloudraker.com/api-keys \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "name": "CI pipeline"
}'
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.organizationSettings.createApiKey({
        name: "CI pipeline",
    });
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.organization_settings.create_api_key(
    name="CI pipeline",
)

```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/api-keys"

	payload := strings.NewReader("{\n  \"name\": \"CI pipeline\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/api-keys")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"name\": \"CI pipeline\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.cloudraker.com/api-keys")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"CI pipeline\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.cloudraker.com/api-keys', [
  'body' => '{
  "name": "CI pipeline"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/api-keys");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"CI pipeline\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["name": "CI pipeline"] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/api-keys")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Request

DELETE [https://api.cloudraker.com/api-keys/\{id}](https://api.cloudraker.com/api-keys/\{id})

```curl
curl -X DELETE https://api.cloudraker.com/api-keys/id \
     -H "Authorization: Bearer <token>"
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.organizationSettings.deleteApiKey({
        id: "id",
    });
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.organization_settings.delete_api_key(
    id="id",
)

```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/api-keys/id"

	req, _ := http.NewRequest("DELETE", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/api-keys/id")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.delete("https://api.cloudraker.com/api-keys/id")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', 'https://api.cloudraker.com/api-keys/id', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/api-keys/id");
var request = new RestRequest(Method.DELETE);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/api-keys/id")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "DELETE"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

`POST /api-keys` returns the plaintext key value **once**, in the `201` response body. Store it immediately. List and get calls never return it again.

### Use a key

Send it as the bearer token on any request:

### Request

GET [https://api.cloudraker.com/me](https://api.cloudraker.com/me)

```curl
curl https://api.cloudraker.com/me \
     -H "Authorization: Bearer <token>"
```

```typescript
import { CloudRakerClient } from "@cloudraker/api";

async function main() {
    const client = new CloudRakerClient({
        token: "YOUR_TOKEN_HERE",
    });
    await client.me.getMe();
}
main();

```

```python
from cloudraker import CloudRaker

client = CloudRaker(
    token="YOUR_TOKEN_HERE",
)

client.me.get_me()

```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.cloudraker.com/me"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.cloudraker.com/me")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.cloudraker.com/me")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.cloudraker.com/me', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.cloudraker.com/me");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.cloudraker.com/me")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

Each gateway instance caches validated key identity for about 60 seconds. A revoked key can keep working for up to one minute before the cache clears. Include this delay in your rotation plan.

## Tenant scoping

One organization is one tenant. After authentication, the gateway derives your tenant from the token's org and scopes **every** call to it. You never send a tenant id, org id, or slug. A token whose identity has no organization gets `404 {"error":"org not found"}`.

## API keys vs. session JWTs

Both token kinds resolve to the same tenant, but they differ on one class of route:

|                               | Organization API key                            | Session JWT                              |
| ----------------------------- | ----------------------------------------------- | ---------------------------------------- |
| Identity                      | The org (a machine credential)                  | A specific human                         |
| Admin / org-level routes      | ✅ Satisfies admin gates                         | ✅ (if the user is an admin)              |
| Per-user resource permissions | ❌ No membership; cannot satisfy per-user grants | ✅ Carries the user's per-resource grants |
| Best for                      | Server-to-server, org-wide automation           | The interactive web app                  |

The gateway treats an org key as an org-level machine credential. The key passes admin and API-key gates, but it has no user membership. Routes gated on a specific human's fine-grained resource permission reject a key. Org admins bypass per-resource space checks in all cases.

## Session JWTs

The web app's auth flow mints session JWTs for a signed-in user. They are three-segment JWTs. The platform verifies them against its signing keys. They carry the user's role, permission claims, and membership id. Do not mint these for server-to-server work. Use an API key instead. This section exists so you recognize them when the web app or an SDK forwards one.

## Failure codes

| Status | Body                           | Meaning                                                                           |
| ------ | ------------------------------ | --------------------------------------------------------------------------------- |
| `401`  | `{"error":"unauthorized"}`     | Missing or malformed `Authorization` header                                       |
| `401`  | `{"error":"invalid_token"}`    | Token failed validation                                                           |
| `404`  | `{"error":"org not found"}`    | Token has no associated organization                                              |
| `503`  | `{"error":"auth_unavailable"}` | Transient auth-service outage during key validation. **Not** a revoked key. Retry |

A `503 auth_unavailable` is deliberately distinct from `401`. During an auth-service outage, a valid key must not look revoked. Treat it as transient and retry with backoff.

## Where to go next

#### [Quickstart](/developers/quickstart)

Make your first authenticated call end to end.

#### [SDKs](/developers/sdks)

Let a typed client set the header for you.