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

# Authentication

> Compare workspace user and system keys, authenticate Tiro API requests, and choose the required scope for each external endpoint.

## API Keys

The Tiro API uses API keys for authentication. Every request carries a valid key in the `Authorization` header as a Bearer token.

Each key belongs to a **workspace**. The key reaches that workspace's resources — its notes, transcripts, summaries, and folders — and nothing outside it. Pick the workspace first, then create the key.

### Key types and permissions

Tiro API keys fall into two types, based on who the request represents.

| Key type                 | How it is identified                                       | What it can do                                                                                                                                         |
| ------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **User key**             | The key is tied to a specific user identity.               | Use it for read and write operations. Actions that change workspace data, such as creating note documents or changing share links, require a user key. |
| **Workspace system key** | The key is tied to the workspace container without a user. | Use it for read access to resources shared with the whole workspace. Voice File Job lifecycle APIs are the only supported write operations.            |

When a workspace system key reads folders, it only returns folders shared with all workspace members. If you need access to private folders, folders shared with specific people, or create, update, or delete actions outside the Voice File Job lifecycle, create and use a user key instead.

<Note>
  If a workspace system key is not bound to a workspace yet, authentication may fail with `401 Unauthorized`. If the key format is correct but 401 responses continue, check which workspace the key is connected to and where it was created.
</Note>

<Warning>
  **Legacy personal keys are going away.** Personal API keys created before workspaces stop working on **June 30, 2026**. Create a workspace key and swap it in before then — see [Legacy personal keys](#legacy-personal-keys-deprecated) below.
</Warning>

## Scopes

Scopes narrow **what a key can do** — a least-privilege setting. You choose them at issuance, and you can change them afterward.

* **A key with no scopes can do everything.** Scopes narrow access rather than grant it — a scoped key can only call the APIs covered by its scopes; anything else is rejected with `403 insufficient_scope`.
* **A `write` scope includes `read` for the same resource.** For example, a key with only `note:write` can also call APIs that require `note:read`.
* Scopes apply to every key type (user, workspace, organization).
* Fetch the assignable scope list from `GET /v1/api-key-scopes`.

### Available scopes

| Scope                         | Description                                                                      |
| ----------------------------- | -------------------------------------------------------------------------------- |
| `note:read`                   | Read note metadata, lists, and transcripts                                       |
| `note:write`                  | Update note metadata such as the title                                           |
| `note_summary:read`           | Read note summaries (one-page documents)                                         |
| `note_document:read`          | Read generated documents                                                         |
| `note_document:write`         | Create documents from custom templates                                           |
| `note_document_template:read` | List and read note document templates                                            |
| `folder:read`                 | Read folders                                                                     |
| `folder:write`                | Create, update, and delete folders                                               |
| `wiki:read`                   | Read and search wiki                                                             |
| `word_memory:read`            | Read word memories                                                               |
| `word_memory:write`           | Create, update, and delete word memories                                         |
| `workspace:read`              | List and read workspaces in your organization                                    |
| `workspace:write`             | Create, rename, set usage limits for, and delete workspaces in your organization |
| `voice_file_job:read`         | Read Voice File Job status, transcripts, translations, and summaries             |
| `voice_file_job:write`        | Create, start, and delete Voice File Jobs                                        |

### Required scope per API

A scoped key may call only the APIs whose scope it holds. A key with no scopes may call every API.

| API                                                                                                                                                                                      | Required scope                |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `GET /v1/external/notes`, `POST /v1/external/notes/search`                                                                                                                               | `note:read`                   |
| `GET /v1/external/notes/{guid}`, `GET /v1/external/notes/{guid}/folders`                                                                                                                 | `note:read`                   |
| `GET /v1/external/notes/{guid}/paragraphs`                                                                                                                                               | `note:read`                   |
| `GET /v1/external/notes/{guid}/summaries`, `.../summaries/{id}`                                                                                                                          | `note_summary:read`           |
| `GET /v1/external/notes/{guid}/share-link`                                                                                                                                               | `note:read`                   |
| `PATCH /v1/external/notes/{guid}`, `PUT /v1/external/notes/{guid}/share-link`, `DELETE .../share-link`                                                                                   | `note:write`                  |
| `GET /v1/external/organizations/me/notes`                                                                                                                                                | `note:read`                   |
| `GET /v1/external/workspaces/{ws}/notes`, `POST .../workspaces/{ws}/notes/search`                                                                                                        | `note:read`                   |
| `GET /v1/external/notes/{guid}/documents`, `.../documents/{id}`                                                                                                                          | `note_document:read`          |
| `POST /v1/external/notes/{guid}/documents`                                                                                                                                               | `note_document:write`         |
| `GET /v1/external/note-document-templates`, `.../managed`, `.../{id}`, `GET /v1/external/{users/me,workspaces/me,workspaces/{ws},workspaces/-,organizations/me}/note-document-templates` | `note_document_template:read` |
| `GET .../folders` (read folders)                                                                                                                                                         | `folder:read`                 |
| `POST/PATCH/PUT/DELETE .../folders` (modify folders)                                                                                                                                     | `folder:write`                |
| `GET /v1/external/workspaces/{ws}/wiki/**`                                                                                                                                               | `wiki:read`                   |
| `GET .../word-memories`, `.../word-memories/{id}` (users·teams·workspaces)                                                                                                               | `word_memory:read`            |
| `POST/PATCH/DELETE .../word-memories`                                                                                                                                                    | `word_memory:write`           |
| `GET /v1/external/organizations/me/workspaces`, `.../workspaces/{ws}`                                                                                                                    | `workspace:read`              |
| `POST /v1/external/organizations/me/workspaces`, `PATCH .../{ws}`, `PATCH .../{ws}/quota`, `DELETE .../{ws}`                                                                             | `workspace:write`             |
| `GET /v1/external/voice-file/jobs/{jobId}`, `GET .../transcript`, `GET .../translations/**`, `GET .../paragraph-summary`                                                                 | `voice_file_job:read`         |
| `POST /v1/external/voice-file/jobs`, `PUT .../{jobId}/upload-complete`, `DELETE .../{jobId}`                                                                                             | `voice_file_job:write`        |
| `GET /v1/external/auth/me`, `.../organizations/me`, `.../workspaces`, `.../workspaces/me`                                                                                                | (no scope — any key)          |

## Getting Your API Key

Create your key from the [Tiro Platform dashboard](https://platform.tiro.ooo/dashboard/api-keys):

<Steps>
  <Step title="Sign in">
    Go to [platform.tiro.ooo/dashboard/api-keys](https://platform.tiro.ooo/dashboard/api-keys).
  </Step>

  <Step title="Pick a workspace">
    Choose the workspace whose data the key should reach, using the workspace switcher in the sidebar. The key is scoped to this workspace only.
  </Step>

  <Step title="Create a key">
    Click **Create New API Key**, name it, and copy the **full key** including the dot — `abc123.xR7mK9pL2qW4...`.
  </Step>

  <Step title="Store it">
    Save it as an environment variable. The secret is shown once and can't be recovered after you close the dialog.
  </Step>
</Steps>

<Warning>
  Keep your API keys secure and never expose them in client-side code. API keys
  should only be used in server-side applications.
</Warning>

## API Key Format

Tiro API keys follow this format:

```
{id}.{secret}
```

For example: `abc123.xR7mK9pL2qW4...`

| Part                    | Example                  | Description                                                                          |
| ----------------------- | ------------------------ | ------------------------------------------------------------------------------------ |
| **Key ID** (`{id}`)     | `abc123`                 | Visible in Platform dashboard. Used to identify which key is making requests.        |
| **Secret** (`{secret}`) | `xR7mK9pL2qW4...`        | Shown only once at creation. The server stores only a hash — it cannot be recovered. |
| **Full API Key**        | `abc123.xR7mK9pL2qW4...` | The **entire string including the dot**. This is what you use as the Bearer token.   |

<Warning>
  **Common mistake:** Do not use just the Key ID (`abc123`) as your Bearer token. You must use the **full key** (`abc123.xR7mK9pL2qW4...`) — the complete string shown when the key was created.
</Warning>

## Making Authenticated Requests

Include your API key in the `Authorization` header of every request:

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -H "Authorization: Bearer $TIRO_API_KEY" \
       -H "Content-Type: application/json" \
       https://api.tiro.ooo/v1/external/notes
  ```

  ```javascript Node.js theme={"system"}
  const response = await fetch("https://api.tiro.ooo/v1/external/notes", {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${process.env.TIRO_API_KEY}`,
      "Content-Type": "application/json",
    },
  });

  const notes = await response.json();
  ```

  ```python Python theme={"system"}
  import os
  import requests

  headers = {
      'Authorization': f'Bearer {os.getenv("TIRO_API_KEY")}',
      'Content-Type': 'application/json'
  }

  response = requests.get('https://api.tiro.ooo/v1/external/notes', headers=headers)
  notes = response.json()
  ```

  ```go Go theme={"system"}
  import (
      "fmt"
      "net/http"
      "os"
  )

  func makeAuthenticatedRequest() (*http.Response, error) {
      client := &http.Client{}
      req, err := http.NewRequest("GET", "https://api.tiro.ooo/v1/external/notes", nil)
      if err != nil {
          return nil, err
      }
      
      apiKey := os.Getenv("TIRO_API_KEY")
      req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
      req.Header.Set("Content-Type", "application/json")
      
      return client.Do(req)
  }
  ```

  ```kotlin Kotlin + Spring theme={"system"}
  @Service
  class TiroApiService {
      
      @Value("\${tiro.api.key}")
      private lateinit var apiKey: String
      
      private val restTemplate = RestTemplate()
      
      fun getNotes(): ResponseEntity<String> {
          val headers = HttpHeaders()
          headers.set("Authorization", "Bearer $apiKey")
          headers.contentType = MediaType.APPLICATION_JSON
          
          val entity = HttpEntity<String>(headers)
          
          return restTemplate.exchange(
              "https://api.tiro.ooo/v1/external/notes",
              HttpMethod.GET,
              entity,
              String::class.java
          )
      }
  }
  ```
</CodeGroup>

## Authentication Errors

If authentication fails, you'll receive a `401 Unauthorized` response. Common reasons include:

* Missing Authorization header
* Malformed key (must be `{id}.{secret}`)
* Unknown key id
* Inactive, expired, or deleted key

```json theme={"system"}
{
  "error": {
    "code": "invalid_api_key",
    "message": "The API key provided is invalid",
    "type": "authentication_error"
  }
}
```

## Legacy personal keys (deprecated)

Before workspaces, a **personal** API key was tied to your account, not a workspace. Those personal keys are deprecated. **Team** keys now come through as workspace keys — each team maps to a workspace. Workspace keys replace both.

<Warning>
  Legacy personal keys stop working on **June 30, 2026**. After that, requests made with one return `401 Unauthorized`. Migrate before then to avoid downtime.
</Warning>

|                   | Legacy personal key                       | Workspace key                                         |
| ----------------- | ----------------------------------------- | ----------------------------------------------------- |
| **Scope**         | Account-wide                              | One workspace                                         |
| **Create new**    | Disabled                                  | Dashboard → pick a workspace → **Create New API Key** |
| **Existing keys** | View and revoke only, until June 30, 2026 | Full lifecycle                                        |
| **Format**        | `{id}.{secret}`                           | `{id}.{secret}` — unchanged                           |

The format is identical, so migrating is a one-line swap — no code rewrite.

### Migrate in three steps

<Steps>
  <Step title="Create a workspace key">
    In the [dashboard](https://platform.tiro.ooo/dashboard/api-keys), select the workspace that holds the notes your integration uses, then create a key.
  </Step>

  <Step title="Swap the secret">
    Replace the value of your `TIRO_API_KEY` environment variable with the new key. No other code changes are needed.
  </Step>

  <Step title="Revoke the legacy key">
    Once traffic runs on the new key, delete the legacy key from the **Legacy personal keys** section of the dashboard.
  </Step>
</Steps>

<Note>
  A legacy key reached every note on your account; a workspace key reaches one workspace. If your data spans several workspaces, create one key per workspace.
</Note>

## Security Best Practices

### Environment Variables

Store API keys securely using environment variables:

<CodeGroup>
  ```bash .env theme={"system"}
  # .env file (never commit this!)
  TIRO_API_KEY=abc123.XYZ...
  ```

  ```javascript Node.js theme={"system"}
  // Load from environment
  const apiKey = process.env.TIRO_API_KEY;
  if (!apiKey) {
    throw new Error('TIRO_API_KEY environment variable is required');
  }
  ```

  ```python Python theme={"system"}
  import os

  # Load from environment with validation
  api_key = os.getenv('TIRO_API_KEY')
  if not api_key:
      raise ValueError('TIRO_API_KEY environment variable is required')
  ```

  ```go Go theme={"system"}
  import (
      "fmt"
      "os"
  )

  func getAPIKey() (string, error) {
      apiKey := os.Getenv("TIRO_API_KEY")
      if apiKey == "" {
          return "", fmt.Errorf("TIRO_API_KEY environment variable is required")
      }
      return apiKey, nil
  }
  ```

  ```kotlin Kotlin + Spring theme={"system"}
  # application.properties
  tiro.api.key=${TIRO_API_KEY}

  # Or application.yml
  tiro:
    api:
      key: ${TIRO_API_KEY}
  ```
</CodeGroup>

### Additional Security Guidelines

* **Rotate keys regularly**: Delete unused keys and generate new ones
* **Separate keys per environment**: Use different keys for development and production
* **Monitor usage**: Track API key usage and rotate on anomalies
* **Never log API keys**: Ensure keys don't appear in application logs
* **Use HTTPS only**: Always make requests over secure connections
