# Быстрый старт

> Создайте OAuth-приложение, получите DPoP-токен и сделайте первый запрос.

## 1. Регистрация

Зайдите на [app.legalstack.ru/register](https://app.legalstack.ru/register) и создайте команду. Вы станете её владельцем: OAuth-приложения создают только владелец и администраторы команды.

## 2. OAuth-приложение

В разделе «Настройки → API» откройте вкладку «OAuth-приложения», нажмите «Новое OAuth-приложение», отметьте grant-тип `client_credentials` и сохраните `client_id` и `client_secret`.

> **Секрет показывается один раз**
>
> Сохраните `client_secret` сразу: после закрытия окна его можно только перевыпустить.

## 3. Ключ DPoP

Каждый токен платформы привязан к ключу клиента: вместе с токеном в каждом запросе передаётся подписанное этим ключом доказательство, см. гайд [Аутентификация](https://docs.legalstack.ru/guides/authentication). Поэтому первым делом создайте пару ключей и функцию, которая подписывает доказательство для конкретного запроса.

Node.js:

```js
import { createHash, randomUUID } from 'node:crypto';
import { exportJWK, generateKeyPair, SignJWT } from 'jose';

const { privateKey, publicKey } = await generateKeyPair('ES256');
const jwk = await exportJWK(publicKey);

const hash = (value) => createHash('sha256').update(value).digest('base64url');

export const proof = (method, url, { nonce, accessToken } = {}) =>
  new SignJWT({
    htm: method,
    htu: url,
    jti: randomUUID(),
    ...(nonce ? { nonce } : {}),
    ...(accessToken ? { ath: hash(accessToken) } : {}),
  })
    .setProtectedHeader({ typ: 'dpop+jwt', alg: 'ES256', jwk })
    .setIssuedAt()
    .sign(privateKey);
```

Python:

```python
import base64, hashlib, json, time, uuid
from jwcrypto import jwk, jwt

key = jwk.JWK.generate(kty="EC", crv="P-256")
public_jwk = json.loads(key.export_public())


def proof(method, url, nonce=None, access_token=None):
    claims = {"htm": method, "htu": url, "iat": int(time.time()), "jti": str(uuid.uuid4())}
    if nonce:
        claims["nonce"] = nonce
    if access_token:
        digest = hashlib.sha256(access_token.encode()).digest()
        claims["ath"] = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
    token = jwt.JWT(header={"typ": "dpop+jwt", "alg": "ES256", "jwk": public_jwk}, claims=claims)
    token.make_signed_token(key)
    return token.serialize()
```

## 4. Получение токена

Первый запрос к точке выдачи токенов получает `400` с `error: use_dpop_nonce` и заголовком `DPoP-Nonce`. Повторите запрос, включив это значение в доказательство, и сохраняйте nonce из каждого следующего ответа.

Node.js:

```js
const TOKEN_URL = 'https://api.legalstack.ru/oauth/token';
let nonce;

async function requestToken() {
  const body = new URLSearchParams({
    grant_type: 'client_credentials',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    scope: 'registry:read tasks:read',
    resource: 'https://api.legalstack.ru',
  });
  const response = await fetch(TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded', DPoP: await proof('POST', TOKEN_URL, { nonce }) },
    body,
  });
  nonce = response.headers.get('DPoP-Nonce') ?? nonce;
  const json = await response.json();
  if (response.status === 400 && json.error === 'use_dpop_nonce') return requestToken();
  return json.access_token;
}

const accessToken = await requestToken();
```

Python:

```python
import requests

TOKEN_URL = "https://api.legalstack.ru/oauth/token"
nonce = None


def request_token():
    global nonce
    response = requests.post(
        TOKEN_URL,
        headers={"DPoP": proof("POST", TOKEN_URL, nonce=nonce)},
        data={
            "grant_type": "client_credentials",
            "client_id": "YOUR_CLIENT_ID",
            "client_secret": "YOUR_CLIENT_SECRET",
            "scope": "registry:read tasks:read",
            "resource": "https://api.legalstack.ru",
        },
    )
    nonce = response.headers.get("DPoP-Nonce", nonce)
    body = response.json()
    if response.status_code == 400 and body.get("error") == "use_dpop_nonce":
        return request_token()
    return body["access_token"]


access_token = request_token()
```

Ответ:

```json
{
  "access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6...",
  "token_type": "DPoP",
  "expires_in": 900,
  "scope": "registry:read tasks:read"
}
```

`expires_in` — время жизни в секундах. Запрашивайте только те scope, которые нужны интеграции: полный список — в [каталоге scope](https://docs.legalstack.ru/api/security/scopes), правила выдачи и обновления — в гайде [Аутентификация](https://docs.legalstack.ru/guides/authentication).

## 5. Первый запрос

Токен передаётся со схемой `DPoP`, а доказательство для запроса включает `ath` — хеш токена — и nonce из последнего ответа. На `401` с кодом `USE_DPOP_NONCE` повторите запрос с новым nonce из заголовка.

Node.js:

```js
async function api(method, path) {
  const url = `https://api.legalstack.ru${path}`;
  const response = await fetch(url, {
    method,
    headers: { Authorization: `DPoP ${accessToken}`, DPoP: await proof(method, url, { nonce, accessToken }) },
  });
  nonce = response.headers.get('DPoP-Nonce') ?? nonce;
  if (response.status === 401 && (await response.clone().json()).code === 'USE_DPOP_NONCE') return api(method, path);
  return response.json();
}

const tasks = await api('GET', '/api/v1/tasks');
```

Python:

```python
def api(method, path):
    global nonce
    url = f"https://api.legalstack.ru{path}"
    response = requests.request(
        method,
        url,
        headers={"Authorization": f"DPoP {access_token}", "DPoP": proof(method, url, nonce=nonce, access_token=access_token)},
    )
    nonce = response.headers.get("DPoP-Nonce", nonce)
    if response.status_code == 401 and response.json().get("code") == "USE_DPOP_NONCE":
        return api(method, path)
    return response.json()


tasks = api("GET", "/api/v1/tasks")
```

Ответ — JSON-массив задач вашей команды. Ошибки приходят в формате Problem Details, см. гайд [Ошибки](https://docs.legalstack.ru/guides/errors).

## Требования к клиенту

> **Только TLS 1.3**
>
> `api.legalstack.ru` и остальные домены платформы принимают только TLS 1.3. Штатные `curl` и PowerShell в Windows 10 не подключаются: используйте curl с OpenSSL, Windows 11, любой современный HTTP-клиент или язык с поддержкой TLS 1.3.

Одного `curl` для работы с API недостаточно: каждому запросу нужно подписанное доказательство DPoP, поэтому используйте язык с библиотекой JOSE, как в примерах выше.

Дальше: [Аутентификация](https://docs.legalstack.ru/guides/authentication), [справочник API](https://docs.legalstack.ru/api) и [обработка ошибок](https://docs.legalstack.ru/guides/errors).
