# Send feedback

`POST https://app.pozyskajpacjenta.pl/api/v1/feedback` · operationId `sendFeedback` · since 1.6.0 · tag Feedback

Sends a remark about the clinic's site or about the API to the platform team, from your frontend's backend or as a browser error report it forwards. Besides the per-minute limit there is a limit of 20 remarks per hour per clinic, counted separately for each source (429 feedback_rate_limited with Retry-After). E-mail addresses, phone numbers, PESEL-like numbers and tokens in addresses (e.g. a patient's /wizyta/<token> link) are removed before storing.

## Request

Authorization: `Bearer <the clinic's key>`.

### Body (application/json)

The remark as JSON.

- `message` (`string`, required): The remark, 3 to 4000 characters.
- `kind` (`string`): uwaga: content or look. potrzeba_api: something your frontend needs from the API. blad: something does not work. One of `uwaga`, `potrzeba_api`, `blad`.
- `category` (`string`): Area: tresc (content), wyglad (look), funkcja (a feature), backend, inne (other). One of `tresc`, `wyglad`, `funkcja`, `backend`, `inne`.
- `source` (`string`): site: the clinic site's backend. client_error: a browser error report your backend forwards. One more value is reserved for the platform team's own tooling. One of `site`, `client_error`.
- `pageUrl` (`string`): The page the remark is about: an absolute address or a path from /.
- `viewport` (`string`): Viewport of the report, e.g. 1440x900.
- `selector` (`string`): CSS selector of the element the remark is about.
- `reporter` (`string`): Who reports it (e.g. a staff member's name). Kept with the remark, left out of what the platform team sees.
- `meta` (`object`): Flat object, up to 30 keys and 4000 characters of JSON: values are text (up to 500 characters), numbers, true/false or null. E.g. userAgent, appVersion.

### cURL

```bash
curl -X POST "https://app.pozyskajpacjenta.pl/api/v1/feedback" \
  -H "Authorization: Bearer $PP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "blad",
    "category": "funkcja",
    "message": "Na telefonie przycisk rezerwacji zasłania stopkę strony zabiegu.",
    "pageUrl": "https://wzorcowa.pozyskajpacjenta.pl/zabiegi/implanty?utm_source=google",
    "viewport": "390x844",
    "selector": "[data-cta=sticky-booking]"
  }'
```

### TypeScript

```ts
import { PozyskajPacjentaClient } from "@pozyskajpacjenta/sdk";

const client = new PozyskajPacjentaClient({
  apiKey: process.env.PP_API_KEY ?? "",
  baseUrl: "https://app.pozyskajpacjenta.pl",
});

const { id, redacted } = await client.sendFeedback({
  kind: "blad",
  category: "funkcja",
  message: "Na telefonie przycisk rezerwacji zasłania stopkę strony zabiegu.",
  pageUrl: "https://wzorcowa.pozyskajpacjenta.pl/zabiegi/implanty?utm_source=google",
  viewport: "390x844",
  selector: "[data-cta=sticky-booking]",
});
```

### PHP

```php
<?php
$body = array(
    'kind'     => 'blad',
    'category' => 'funkcja',
    'message'  => 'Na telefonie przycisk rezerwacji zasłania stopkę strony zabiegu.',
    'pageUrl'  => 'https://wzorcowa.pozyskajpacjenta.pl/zabiegi/implanty?utm_source=google',
    'viewport' => '390x844',
    'selector' => '[data-cta=sticky-booking]',
);

$response = wp_remote_post(
    'https://app.pozyskajpacjenta.pl/api/v1/feedback',
    array(
        'headers' => array(
            'Authorization' => 'Bearer ' . getenv( 'PP_API_KEY' ),
            'Content-Type'  => 'application/json',
        ),
        'body'    => wp_json_encode( $body ),
        'timeout' => 10,
    )
);
if ( is_wp_error( $response ) ) {
    error_log( $response->get_error_message() );
    return;
}
$status = wp_remote_retrieve_response_code( $response );
$data   = json_decode( wp_remote_retrieve_body( $response ), true );
```

## Response

### 201: The remark was accepted.

- `id` (`string`, required): Id of the remark, fb_…
- `status` (`string`, required): new: accepted; it is passed on to the platform team. One of `new`, `synced`, `ignored`.
- `redacted` (`string[]`): Only when something was removed before storing: email, phone, pesel. One of `email`, `phone`, `pesel`.

Example (Remark accepted):

```json
{
  "id": "fb_YRNxN-wdvwgXufTq",
  "status": "new"
}
```

Example (Accepted after removing an e-mail and a phone number):

```json
{
  "id": "fb_m3-ehVwoPDTP7PMR",
  "status": "new",
  "redacted": [
    "email",
    "phone"
  ]
}
```

## Errors

| Status | code | When |
| --- | --- | --- |
| 400 | `invalid_json` | The body is not JSON |
| 400 | `invalid_body` | A field failed validation |
| 401 |  | No Authorization header |
| 401 |  | Unknown or rotated key |
| 429 | `feedback_rate_limited` | The hourly limit for this source is used up |
| 429 |  | Over 120 requests in this minute |

400 (The body is not JSON):

```json
{
  "error": "invalid JSON",
  "code": "invalid_json"
}
```

400 (A field failed validation):

```json
{
  "error": "category: Invalid option: expected one of \"tresc\"|\"wyglad\"|\"funkcja\"|\"backend\"|\"inne\"",
  "code": "invalid_body"
}
```

401 (No Authorization header):

```json
{
  "error": "missing Authorization: Bearer <klucz z panelu Ustawienia>"
}
```

401 (Unknown or rotated key):

```json
{
  "error": "invalid key"
}
```

429 (The hourly limit for this source is used up):

```json
{
  "error": "przekroczono limit 20 zgłoszeń na godzinę",
  "code": "feedback_rate_limited"
}
```

429 (Over 120 requests in this minute):

```json
{
  "error": "przekroczono limit 120 zapytań/min"
}
```

## Rate limit

- 120 requests per minute per key, shared by every endpoint.
- 20 remarks per hour per clinic, counted separately for each source.

## SDK method

`client.sendFeedback(input: FeedbackInput): Promise<FeedbackCreated>`

Throws ApiError 429 with `code: "feedback_rate_limited"` over the hourly limit.
