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

# Server API

> Fingerprint Server API enables you to get more information about your visitors or about individual identification events.

Server API enables you to retrieve, search, and update identification event data, and manage visitor data. It is designed for use only from server-side environments — never directly from client-facing surfaces such as browsers or mobile devices.

Server API requests are not billed and do not count towards your monthly allowance.

The following Server APIs are available:

* [`/v4/events/:event_id`](/reference/server-api-get-event)
  * [GET](/reference/server-api-get-event) a detailed payload for a **single** event defined by an `event_id`
  * [PATCH](/reference/server-api-update-event) an event with `linked_id`, `tag` and `suspect` flag

* [`/v4/events`](/reference/server-api-search-events)
  * [GET](/reference/server-api-search-events)  a list of event details using on a wide range of built-in filters (`linked_id`, `visitor_id`, `start`, `end` and `suspect` fields among others).

* [`/v4/events/:event_id/feedback`](/reference/server-api-submit-event-feedback)
  * [POST](/reference/server-api-submit-event-feedback) feedback for an identification event

* [`/v4/visitors/:visitor_id`](/reference/server-api-delete-visitor-id)
  * [DELETE](/reference/server-api-delete-visitor-id) all data associated with a specific visitor ID

### See Also

* To identify the browsers that visit your web application, see our documentation for [JavaScript agent](/reference/js-agent).
* To identify the mobile devices that use your mobile app, see our documentation for [Android](/docs/native-android-integration) and [iOS](/docs/ios).

## Regions

The server API is available in the **Global**, **EU** and **Asia (Mumbai)** regions:

| Region        | Base URL                 | Server Location    |
| ------------- | ------------------------ | ------------------ |
| Global        | `https://api.fpjs.io`    | Global             |
| EU            | `https://eu.api.fpjs.io` | Frankfurt, Germany |
| Asia (Mumbai) | `https://ap.api.fpjs.io` | Mumbai, India      |

Use the **Base URL** dropdown in the right column of each endpoint to select the correct base URL according to your workspace region.

## Authentication

Use an `Authorization: Bearer` header to authenticate to the API. All unauthenticated requests will return a `HTTP 403 Forbidden` response.

### Authorization: Bearer HTTP header

When making an API request, add the `Authorization: Bearer SECRET_API_KEY` HTTP header with your [secret API key](https://dashboard.fingerprint.com/api-keys?type=api).

Example request including the API key in the `Authorization` header

<CodeGroup>
  ```bash bash theme={"theme":"github-dark-dimmed"}
  curl https://api.fpjs.io/v4/events/EVENT_ID  \
       -H `Authorization: Bearer SECRET_API_KEY`
  ```
</CodeGroup>

## Query parameter syntax

For query parameters with multiple values, use the repeated keys syntax (`parameter=value1&parameter=value2`).
Other notations like comma-separated (`parameter=value1,value2`) or bracket notation (`parameter[]=value1&parameter[]=value2`) are not supported.

## Rate limiting

If you exceed the rate limit when making a Server API call, you'll get an HTTP 429 error:

```json JSON theme={"theme":"github-dark-dimmed"}
{
  "error": {
    "code": "too_many_requests",
    "message": "too many requests"
  }
}
```

Retry the request after a short interval. The response may include a `Retry-After` header indicating how long to wait.

## Error handling

Handle Server API responses according to the error type instead of retrying every failure the same way.

| HTTP status | Meaning                                                           | Recommended action                                                                  |
| ----------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `400`       | Invalid parameter such as a malformed ID, date, or pagination key | Fix the request and do not retry as-is                                              |
| `403`       | Missing or invalid secret API key                                 | Check the API key and retry only after fixing it                                    |
| `404`       | Event, visitor, or other resource not found                       | Confirm the identifier exists in your workspace                                     |
| `429`       | Rate limited or the service is under heavy load                   | Retry with exponential backoff and honor a numeric `Retry-After` delay when present |
| `504`       | Query timed out                                                   | Narrow the time range or add a more specific filter before retrying                 |

### Retry with backoff for `429`

```python theme={"theme":"github-dark-dimmed"}
import time, requests

def call_server_api(url, params=None, headers=None, max_retries=5):
    delay = 1.0
    for attempt in range(max_retries):
        response = requests.get(url, params=params, headers=headers)
        if response.status_code != 429:
            return response

        retry_after = response.headers.get("Retry-After")
        if retry_after and retry_after.isdigit():
            delay = max(delay, float(retry_after))

        if attempt == max_retries - 1:
            break

        time.sleep(delay)
        delay = min(delay * 2, 60)

    return response
```

## Best practices

Use these guidelines when querying [`GET /v4/events`](/reference/server-api-search-events) so searches stay fast, predictable, and complete.

### Overview

Keep these limits and defaults in mind:

* **Default time window**: The API searches the last 7 days when `start` and `end` are omitted.
* **Maximum lookback**: Depending on your [Fingerprint plan](/docs/billing#account-limits), searches can look back 30-90 days.
* **Page size cap**: `limit` is capped at 500 events per request.
* **Query timeout**: Broad queries can return `504 Gateway Timeout` after 30 seconds.
* **Rate limiting**: Under load, the API can return `429 Too Many Requests`, so your integration should retry with backoff.

### 1. Retrieve a single known event with `GET /v4/events/{event_id}`

If you already know the `event_id`, use [Get event by event ID](/reference/server-api-get-event) instead of the search endpoint:

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events/EVENT_ID
```

Use it for:

* Server-side verification of a specific identification result
* Fetching full Smart Signals for one event
* Confirming an event exists before taking action on it

### 2. Always filter by an indexed field

The most important performance rule is to include at least one of `visitor_id`, `linked_id`, or `ip_address`.

Without one of these filters, the API must scan all events for your workspace across the requested time range. On large workspaces, that is the most common cause of `504` timeouts.

| Filter       | Use case                                                                      |
| ------------ | ----------------------------------------------------------------------------- |
| `visitor_id` | Look up the history of a specific browser or device                           |
| `linked_id`  | Look up events tied to your internal user, session, or transaction ID         |
| `ip_address` | Look up events from a specific IP address or CIDR range such as `10.0.0.0/24` |

**Good: scoped query**

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?visitor_id=VISITOR_ID&start=2024-01-01T00:00:00Z&end=2024-01-02T00:00:00Z
```

**Avoid: broad unscoped query**

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?start=2024-01-01T00:00:00Z&end=2024-01-31T00:00:00Z
```

### 3. Use narrow time ranges

Always provide both `start` and `end`, and keep the time window as narrow as your use case allows.

* The default 7-day window is useful for recent lookups, but explicit timestamps are more predictable.
* For monitoring and alerting, query the last few minutes rather than hours.
* For historical exports, split the work into smaller windows such as one day at a time.

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?visitor_id=VISITOR_ID&start=2024-06-01T00:00:00Z&end=2024-06-02T00:00:00Z
```

### 4. Use cursor-based pagination for large result sets

When a response includes `pagination_key`, repeat the same request with that value until no new `pagination_key` is returned.

Do not adjust `start` and `end` to simulate pagination. That can miss or duplicate events, especially when multiple events share the same timestamp.

**First page**

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?visitor_id=VISITOR_ID&start=2024-06-01T00:00:00Z&end=2024-06-02T00:00:00Z&limit=100
```

**Next page**

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?visitor_id=VISITOR_ID&start=2024-06-01T00:00:00Z&end=2024-06-02T00:00:00Z&limit=100&pagination_key=PAGINATION_KEY
```

Notes:

* The pagination key is an opaque cursor. Do not parse or modify it.
* Do not store pagination keys for long-term reuse.
* If you receive a `400` invalid pagination key error, restart pagination from the beginning.

### 5. Use `reverse=true` for chronological processing

By default, search results are returned newest first. If you are replaying events into a processing pipeline or data warehouse, use `reverse=true` so the oldest events are returned first.

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?visitor_id=VISITOR_ID&start=2024-06-01T00:00:00Z&end=2024-06-02T00:00:00Z&reverse=true&limit=100
```

### 6. Use built-in filters to reduce result size

Use the search endpoint's filters to narrow results before they are returned instead of filtering client-side.

| Filter         | Values                       | Use case                                                                               |
| -------------- | ---------------------------- | -------------------------------------------------------------------------------------- |
| `bot`          | `all`, `good`, `bad`, `none` | Exclude or isolate bot traffic                                                         |
| `suspect`      | `true`, `false`              | [Find events you previously flagged as fraudulent](/reference/server-api-update-event) |
| `vpn`          | `true`, `false`              | Isolate events where a VPN was detected                                                |
| `url`          | URL string                   | Filter by the page or app where identification ran                                     |
| `bundle_id`    | iOS bundle ID                | Filter by iOS app                                                                      |
| `package_name` | Android package              | Filter by Android app                                                                  |
| `asn`          | ASN number string            | Filter by network or carrier                                                           |

```http theme={"theme":"github-dark-dimmed"}
GET /v4/events?suspect=true&start=2024-06-01T00:00:00Z&end=2024-06-02T00:00:00Z
```

### 7. Understand environment scoping

If your workspace uses [environments](/docs/multiple-environments), search results are automatically scoped by the API key you use:

* A workspace-scoped API key returns events across all environments.
* An environment-scoped API key returns only events from that environment.

If you expect events but receive an empty response, confirm that the API key matches the environment where identification ran.

### 8. Use day-by-day iteration for bulk exports

The Events API is a search API, not a streaming export API. For large exports:

1. Iterate by day instead of querying the full time range at once.
2. Include a filter such as `visitor_id`, `linked_id`, `ip_address`, or `url` whenever possible.
3. Follow `pagination_key` within each window until it is exhausted.
4. Add a small delay between paginated requests to reduce the chance of `429` responses.
5. Use `reverse=true` for append-only processing so restarts remain idempotent.

## Server API SDKs

For a smoother developer experience, we offer typed SDKs for these languages:

* [Node SDK](https://github.com/fingerprintjs/node-sdk)
* [PHP SDK](https://github.com/fingerprintjs/php-sdk)
* [Python SDK](https://github.com/fingerprintjs/python-sdk)
* [C# SDK](https://github.com/fingerprintjs/dotnet-sdk/)
* [Java SDK](https://github.com/fingerprintjs/java-sdk)
* [Go SDK](https://github.com/fingerprintjs/go-sdk)

Pick one of them as the **Language** on the endpoint page's top right corner to see an example request using that SDK.

The SDKs (and this reference) are based on a Server API OpenAPI schema, which is also available on GitHub:

* [Server API OpenAPI schema](https://github.com/fingerprintjs/fingerprint-pro-server-api-openapi)

## Trying it out

You can try calling the Server API directly from this reference:

1. You are going to need a Secret API Key. You can create one in your Fingerprint **Dashboard** > [**API Keys**](https://dashboard.fingerprint.com/api-keys).
2. To make a request, you will need an `event_id` of an identification event associated with your workspace. Go to **Dashboard** > [**Identification**](https://dashboard.fingerprint.com/visits) to see your identification events.
3. Scroll down to one of the endpoints, for example, [Get event by event ID](/reference/server-api-get-event).
4. Set **Authentication** to your secret API key.
5. Set the **event\_id** path parameter to some `event_id` from your dashboard.
6. Make sure the **Base URL** corresponds to your workspace region.
7. Click **Try it!**

A real API response will appear in the **Response** section. Alternatively, you can view the prepared response examples there.
