# Аутентификация

Как аутентифицировать ваши API-запросы к Statsnet.

Statsnet аутентифицирует API-запросы с помощью API-ключей. Ключ выглядит как `sk_live_...` и передаётся в одном из двух заголовков — они эквивалентны:

**Заголовки аутентификации**

```bash
x-api-key: sk_live_YOUR_KEY
# или
Authorization: Bearer sk_live_YOUR_KEY
```

## Как получить ключ

Ключи создаются в [личном кабинете](https://statsnet.co/me) — до 10 на аккаунт. Полное значение ключа показывается **один раз** при создании; сохраните его в надёжном месте. Ключи можно переименовывать и отзывать, а также смотреть расход по каждому ключу за текущий месяц.

Если у вас ещё нет аккаунта Statsnet, [зарегистрируйтесь](https://statsnet.co/auth?type=signup) — это займёт несколько минут.

## Что доступно по ключу

Ключ наследует подписку аккаунта, который его создал:

- **Без активного тарифа** — ключ работает, но только на публичных эндпоинтах (карточка `meta/beta`, поиск, markdown-карточка).
- **Тарифы с отчётами** (Basic, Premium, Enterprise) — платные эндпоинты списывают один отчёт за компанию; повторный просмотр той же компании в течение 2 недель бесплатен.
- **API-тарифы** — каждый вызов метерится против месячного лимита с ограничением частоты. Текущий расход возвращается в каждом ответе в заголовках `X-API-Calls-Used` и `X-API-Calls-Limit`; превышение лимита возвращает `429`.

## Примеры кода

**Пример API-запроса**

**Python**

```python
import requests

url = 'https://statsnet.co/api/v1/companies/search'
headers = {
  'Authorization': 'Bearer sk_live_YOUR_KEY',
  'Content-Type': 'application/json'
}
body = {'query': 'kaspi', 'filters': {'jurisdiction': ['kz']}}

response = requests.post(url, json=body, headers=headers).json()
print(response)
```

**TypeScript**

```typescript
const url = 'https://statsnet.co/api/v1/companies/search';

const response = await fetch(url, {
method: 'POST',
headers: {
  'Authorization': 'Bearer sk_live_YOUR_KEY',
  'Content-Type': 'application/json',
},
body: JSON.stringify({ query: 'kaspi', filters: { jurisdiction: ['kz'] } }),
});

const data = await response.json();
console.log(data);
```

**Go**

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"io"
)

func main() {
	url := "https://statsnet.co/api/v1/companies/search"
	body, _ := json.Marshal(map[string]any{
		"query":   "kaspi",
		"filters": map[string]any{"jurisdiction": []string{"kz"}},
	})

	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
	req.Header.Set("Authorization", "Bearer sk_live_YOUR_KEY")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, _ := client.Do(req)
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)
	fmt.Println(string(respBody))
}
```

**Rust**

```rust
use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
  let client = Client::new();
  let response = client
      .post("https://statsnet.co/api/v1/companies/search")
      .header("Authorization", "Bearer sk_live_YOUR_KEY")
      .header("Content-Type", "application/json")
      .json(&json!({"query": "kaspi", "filters": {"jurisdiction": ["kz"]}}))
      .send()
      .await?
      .text()
      .await?;

  println!("{}", response);
  Ok(())
}
```

**C#**

```csharp
using System.Net.Http;
using System.Text;
using System.Text.Json;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post,
  "https://statsnet.co/api/v1/companies/search");

request.Headers.Add("Authorization", "Bearer sk_live_YOUR_KEY");
request.Content = new StringContent(
  JsonSerializer.Serialize(new { query = "kaspi" }),
  Encoding.UTF8, "application/json");

var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
```

**C++**

```cpp
#include <iostream>
#include <curl/curl.h>
#include <string>

size_t WriteCallback(void* contents, size_t size,
                   size_t nmemb, std::string* output) {
  output->append((char*)contents, size * nmemb);
  return size * nmemb;
}

int main() {
  CURL* curl = curl_easy_init();
  std::string response;

  struct curl_slist* headers = nullptr;
  headers = curl_slist_append(headers,
      "Authorization: Bearer sk_live_YOUR_KEY");
  headers = curl_slist_append(headers,
      "Content-Type: application/json");

  const char* body = R"({"query":"kaspi"})";

  curl_easy_setopt(curl, CURLOPT_URL,
      "https://statsnet.co/api/v1/companies/search");
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

  curl_easy_perform(curl);
  std::cout << response << std::endl;

  curl_slist_free_all(headers);
  curl_easy_cleanup(curl);
  return 0;
}
```

## Обработка ошибок

Неверный, отозванный или отсутствующий ключ на защищённом эндпоинте возвращает JSON-ошибку с соответствующим HTTP-статусом:

**Error response**

```json
{
  "status": 403,
  "message": "access denied"
}
```

| Статус | Значение |
| --- | --- |
| `401` | Сессия или учётные данные истекли. |
| `402` | У аккаунта ключа нет активной подписки (отменена или истекла). |
| `403` | Нет доступа — анонимный запрос к защищённому эндпоинту или недостаточный тариф. |
| `429` | Исчерпан месячный лимит API-вызовов или превышена частота. Смотрите `X-API-Calls-Used` / `X-API-Calls-Limit`. |
