API reference

Generated from the pinned OpenAPI 1.0.0 specification. This is a release contract, not confirmation of the public API version: check your environment's GET /v1/capabilities before enabling features. openapi.yaml · llms-full.txt

First stable API contract 1.0.0, including optional services. Net source commit: da2b28b41924. Site and Via release versions are independent of the API contract version.

Order commits require a separate X-Commerce-Key even at zero price. Live partner enrollment is explicit; legacy superlike/rewind behavior remains until then. Services and enrollment

Access

Base URLhttps://api.pereval.dating — for test and live; the key selects the environment
AuthAuthorization: Bearer <api_key> — server-side only
FormatJSON, UTF-8, times RFC 3339 UTC
Errors{"error": "<code>", "message": "…"} — all codes
Rate limit600 requests/minute per platform; feed — 200 profiles per member a day

Members

Push, update and remove your users' network profiles.

Add or update members in a batch

PUT/v1/members

Up to 100 members per request, each validated on its own — one bad profile doesn't block the rest. Use it for the first upload of users who turned the network on.

curl -X PUT 'https://api.pereval.dating/v1/members' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "external_id": "string",
      "consent": {
        "network": true,
        "cross_language": false,
        "world": false,
        "given_at": "2026-10-24T12:00:00Z",
        "text_version": "string"
      },
      "display_name": "string",
      "birth_year": 1,
      "gender": "male",
      "age_min": 18,
      "age_max": 1,
      "languages": [
        "string"
      ],
      "city": {
        "name": "string",
        "country": "string",
        "lat": 1.5,
        "lon": 1.5
      },
      "bio": "string",
      "photos": [
        "https://example.com/photo.jpg"
      ],
      "videos": [
        {
          "url": "https://example.com/photo.jpg",
          "poster": "https://example.com/photo.jpg"
        }
      ],
      "photo_verified": false,
      "verification": {
        "method": "selfie",
        "provider": "string",
        "verified_at": "2026-10-24T12:00:00Z",
        "check_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
      },
      "intents": [
        "serious",
        "casual",
        "friendship"
      ],
      "arrangement_role": "provider",
      "last_active_at": "2026-10-24T12:00:00Z",
      "height_cm": 120,
      "show_distance": false
    }
  ]
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/members');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'items' => [[
            'external_id' => 'string',
            'consent' => [
                'network' => true,
                'cross_language' => false,
                'world' => false,
                'given_at' => '2026-10-24T12:00:00Z',
                'text_version' => 'string',
            ],
            'display_name' => 'string',
            'birth_year' => 1,
            'gender' => 'male',
            'age_min' => 18,
            'age_max' => 1,
            'languages' => ['string'],
            'city' => [
                'name' => 'string',
                'country' => 'string',
                'lat' => 1.5,
                'lon' => 1.5,
            ],
            'bio' => 'string',
            'photos' => ['https://example.com/photo.jpg'],
            'videos' => [[
                'url' => 'https://example.com/photo.jpg',
                'poster' => 'https://example.com/photo.jpg',
            ]],
            'photo_verified' => false,
            'verification' => [
                'method' => 'selfie',
                'provider' => 'string',
                'verified_at' => '2026-10-24T12:00:00Z',
                'check_id' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
            ],
            'intents' => ['serious', 'casual', 'friendship'],
            'arrangement_role' => 'provider',
            'last_active_at' => '2026-10-24T12:00:00Z',
            'height_cm' => 120,
            'show_distance' => false,
        ]],
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.put(
    "https://api.pereval.dating/v1/members",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "items": [
            {
                "external_id": "string",
                "consent": {
                    "network": True,
                    "cross_language": False,
                    "world": False,
                    "given_at": "2026-10-24T12:00:00Z",
                    "text_version": "string"
                },
                "display_name": "string",
                "birth_year": 1,
                "gender": "male",
                "age_min": 18,
                "age_max": 1,
                "languages": [
                    "string"
                ],
                "city": {
                    "name": "string",
                    "country": "string",
                    "lat": 1.5,
                    "lon": 1.5
                },
                "bio": "string",
                "photos": [
                    "https://example.com/photo.jpg"
                ],
                "videos": [
                    {
                        "url": "https://example.com/photo.jpg",
                        "poster": "https://example.com/photo.jpg"
                    }
                ],
                "photo_verified": False,
                "verification": {
                    "method": "selfie",
                    "provider": "string",
                    "verified_at": "2026-10-24T12:00:00Z",
                    "check_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
                },
                "intents": [
                    "serious",
                    "casual",
                    "friendship"
                ],
                "arrangement_role": "provider",
                "last_active_at": "2026-10-24T12:00:00Z",
                "height_cm": 120,
                "show_distance": False
            }
        ]
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "items": [
      {
        "external_id": "string",
        "consent": {
          "network": true,
          "cross_language": false,
          "world": false,
          "given_at": "2026-10-24T12:00:00Z",
          "text_version": "string"
        },
        "display_name": "string",
        "birth_year": 1,
        "gender": "male",
        "age_min": 18,
        "age_max": 1,
        "languages": [
          "string"
        ],
        "city": {
          "name": "string",
          "country": "string",
          "lat": 1.5,
          "lon": 1.5
        },
        "bio": "string",
        "photos": [
          "https://example.com/photo.jpg"
        ],
        "videos": [
          {
            "url": "https://example.com/photo.jpg",
            "poster": "https://example.com/photo.jpg"
          }
        ],
        "photo_verified": false,
        "verification": {
          "method": "selfie",
          "provider": "string",
          "verified_at": "2026-10-24T12:00:00Z",
          "check_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
        },
        "intents": [
          "serious",
          "casual",
          "friendship"
        ],
        "arrangement_role": "provider",
        "last_active_at": "2026-10-24T12:00:00Z",
        "height_cm": 120,
        "show_distance": false
      }
    ]
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "items": [
    {
      "external_id": "string",
      "consent": {
        "network": true,
        "cross_language": false,
        "world": false,
        "given_at": "2026-10-24T12:00:00Z",
        "text_version": "string"
      },
      "display_name": "string",
      "birth_year": 1,
      "gender": "male",
      "age_min": 18,
      "age_max": 1,
      "languages": [
        "string"
      ],
      "city": {
        "name": "string",
        "country": "string",
        "lat": 1.5,
        "lon": 1.5
      },
      "bio": "string",
      "photos": [
        "https://example.com/photo.jpg"
      ],
      "videos": [
        {
          "url": "https://example.com/photo.jpg",
          "poster": "https://example.com/photo.jpg"
        }
      ],
      "photo_verified": false,
      "verification": {
        "method": "selfie",
        "provider": "string",
        "verified_at": "2026-10-24T12:00:00Z",
        "check_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
      },
      "intents": [
        "serious",
        "casual",
        "friendship"
      ],
      "arrangement_role": "provider",
      "last_active_at": "2026-10-24T12:00:00Z",
      "height_cm": 120,
      "show_distance": false
    }
  ]
}`)

req, _ := http.NewRequestWithContext(ctx, "PUT", "https://api.pereval.dating/v1/members", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
itemsrequired
object[]
1–100 items
external_idrequired
string
Your user id · 1–128 chars
consentrequired
object
networkrequired
boolean
The user enabled "show me in the network". false hides the member (see the upsert) instead of being rejected
cross_language
boolean
The user agreed to chat with people speaking other languages (machine translation) · default false
world
boolean
The user wants to meet people from other cities and countries — the scope=world feed. Opt-in only, off by default · default false
given_atrequired
string (date-time)
When the user actually turned it on
text_versionrequired
string
Version of the consent text the user saw, e.g. its date
display_namerequired
string
1–50 chars
birth_yearrequired
integer
Members must be 18+
genderrequired
"male" | "female"
Matches are always of the opposite gender; there is no "seeking" field (ignored if sent)
age_minrequired
integer
≥ 18
age_maxrequired
integer
languagesrequired
string[]
First is the main language: profiles and messages are translated into it. Languages listed here are never translated for this member. Translation languages — GET /v1/languages (38 in v0.8); other ISO 639-1 codes are accepted, names such as Russian are rejected with 422. · ≥ 1 items
cityrequired
object
Name or coordinates required. Can change once a day.
name
string
country
string
ISO 3166-1 alpha-2
lat
number
lon
number
bio
string
≤ 1000 chars
photosrequired
string (uri)[]
Public HTTPS URLs; 1–9 while visible, may be empty while hidden · ≤ 9 items
videos
object[]
Short clips shown on the card next to photos, not instead of them. Omit or send [] to clear. production rejects all nonempty video input with 422 until frame moderation exists. Inspect the videos capability; the following format is for isolated development only. Each clip is an MP4 up to 15 seconds and 20 MB, plus a poster image. The file stays on your host. There is no mute flag: the viewing app starts clips silent and has one switch for every clip. Posters are moderated like photos. A clip that is not an MP4, is longer, or cannot be downloaded rejects the profile (video_invalid, video_too_long, video_unavailable). · ≤ 3 items
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verified
boolean
Before 0.15 — "you verified the person matches the photos". Still accepted: without verification it is a claim with an unspecified method made when the network receives it. Send verification instead. · default false
verification
object
How and when you checked that the person matches their photos. The network turns it into the photo_verified mark that other apps see, if (1) the network has approved your verification method — describe it when you connect, the network reviews it once — and (2) none of the current photos is newer than verified_at. Photos the person joins the network with are covered by an earlier check too; a photo added later switches the mark off until you send a newer verified_at. The same picture under a new URL is recognised by its content. The network re-checks a sample of verified people and those with fake signals (reports, the same photo on another person, generated images); if your marks fail, the network stops counting them for all your users. Omit (and photo_verified: false) — no claim. — or a claim by the network's photo check: {"provider": "pereval", "check_id": "…"}. The network verifies that the check is yours, has passed and covers all current photos (each with a match or no_face verdict, at least one match, recognised by URL or content); then the mark counts even if your own method isn't approved, and verified_at is the time of the check. method and verified_at are then ignored.
method
"selfie" | "video" | "liveness" | "document" | "in_person" | "other"
selfie — a photo selfie (e.g. with a gesture); video — a video selfie; liveness — an automated liveness check; document — an ID with a face match; in_person — met in person
provider
string
Who did the check: a vendor (e.g. Sumsub), own, or pereval — the network's photo check (with check_id) · ≤ 60 chars
verified_at
string (date-time)
When the check passed. Not in the future.
check_id
string (uuid)
id of a passed photo-check session of yours, less than 60 days from original creation. An expired/deleted reference returns 422 invalid; do not retry it or automatically create a replacement check. Omit a stale reference on subsequent upserts. Already accepted verification of unchanged photos is independent of case material retention; expiry does not approve any new photos.
intents
"serious" | "casual" | "friendship" | "arrangement"[]
What the member is looking for. Two members are matched only if they share an intent. arrangement — a relationship with a well-off partner — is opt-in only, requires your app to have the category enabled, and requires arrangement_role. Members whose only shared intent is arrangement are matched only with the opposite role. Offering money for sex is forbidden in every category. · default ["serious","casual","friendship"]
arrangement_role
"provider" | "seeker"
With the arrangement intent: provider — ready to support a partner; seeker — looking for a well-off partner
last_active_at
string (date-time)
height_cm
integer
Height in cm · 120–230
show_distance
boolean
Others may see distance_km to this person. Off by default. Does not change the city. · default false

Responses

200 Per-item results in the same order
FieldDescription
itemsrequired
object[]
external_idrequired
string
network_id
string (uuid)
Present when stored
photo_verified
boolean
Whether the verification mark counts — see the single upsert
verification
"accepted" | "none" | "no_photos" | "partner_not_approved" | "photos_changed"
Why the verification mark counts or not: accepted; none — no claim; no_photos; partner_not_approved — the network hasn't approved your verification method yet (never for claims by the network's photo check, ); photos_changed — a photo is newer than verified_at: check the person again and send a newer verified_at.
error
object
Present when this item was rejected
code
"invalid" | "location_change_limited"
message
string
422 Validation failed; message says which field

Add or update a member

PUT/v1/members/{external_id}

Idempotent upsert by external_id. Call it when a user opts in and whenever their profile, photos, preferences or consents change.

Hidden members. consent.network: false hides the member instead of removing them: the same network_id, matches, conversations, dislikes and blocks stay; the member is not in anyone's feed and gets no feed (409 member_hidden); they can only like back someone who already liked them (that is a match), no super likes. Messages in existing matches flow both ways. Photos are optional while hidden. Send consent.network: true again and the member is visible with all their history. Use this when the user turns the network off; use DELETE only to erase their network data (an explicit "delete my data from the network" or account deletion). Coordinates are rounded to 0.01° (~1 km) by the network; send city-level precision anyway. The city may change once a day: a new city (or a move over 50 km) sooner than 24 h after the previous change returns 409 location_change_limited. The bio's language is detected by the network; the bio is translated on demand into the main languages of members who are reading the feed.

Moderation. A new profile, or one with changed photos, name or bio, is checked before it appears in feeds — usually within seconds. Explicit photos, insults or obscenity, contact details (phone, link, messenger handle) and photos that cannot be downloaded are not allowed. On rejection you get member.rejected with the reason; the profile is hidden and cannot like until you send a corrected one (resending the same profile does not trigger a new check). Photos and text that already passed are not checked again.

curl -X PUT 'https://api.pereval.dating/v1/members/user-123' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "consent": {
    "network": true,
    "cross_language": true,
    "world": false,
    "given_at": "2026-10-24T12:00:00Z",
    "text_version": "2026-10-24"
  },
  "display_name": "Anna",
  "birth_year": 1994,
  "gender": "female",
  "age_min": 28,
  "age_max": 40,
  "languages": [
    "ru",
    "en"
  ],
  "city": {
    "name": "Moscow",
    "country": "RU",
    "lat": 55.75,
    "lon": 37.62
  },
  "bio": "Hiking, jazz and good coffee",
  "photos": [
    "https://cdn.example-dating.app/u/123/1.jpg"
  ],
  "photo_verified": true,
  "height_cm": 168
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'consent' => [
            'network' => true,
            'cross_language' => true,
            'world' => false,
            'given_at' => '2026-10-24T12:00:00Z',
            'text_version' => '2026-10-24',
        ],
        'display_name' => 'Anna',
        'birth_year' => 1994,
        'gender' => 'female',
        'age_min' => 28,
        'age_max' => 40,
        'languages' => ['ru', 'en'],
        'city' => [
            'name' => 'Moscow',
            'country' => 'RU',
            'lat' => 55.75,
            'lon' => 37.62,
        ],
        'bio' => 'Hiking, jazz and good coffee',
        'photos' => ['https://cdn.example-dating.app/u/123/1.jpg'],
        'photo_verified' => true,
        'height_cm' => 168,
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.put(
    "https://api.pereval.dating/v1/members/user-123",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "consent": {
            "network": True,
            "cross_language": True,
            "world": False,
            "given_at": "2026-10-24T12:00:00Z",
            "text_version": "2026-10-24"
        },
        "display_name": "Anna",
        "birth_year": 1994,
        "gender": "female",
        "age_min": 28,
        "age_max": 40,
        "languages": [
            "ru",
            "en"
        ],
        "city": {
            "name": "Moscow",
            "country": "RU",
            "lat": 55.75,
            "lon": 37.62
        },
        "bio": "Hiking, jazz and good coffee",
        "photos": [
            "https://cdn.example-dating.app/u/123/1.jpg"
        ],
        "photo_verified": True,
        "height_cm": 168
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "consent": {
      "network": true,
      "cross_language": true,
      "world": false,
      "given_at": "2026-10-24T12:00:00Z",
      "text_version": "2026-10-24"
    },
    "display_name": "Anna",
    "birth_year": 1994,
    "gender": "female",
    "age_min": 28,
    "age_max": 40,
    "languages": [
      "ru",
      "en"
    ],
    "city": {
      "name": "Moscow",
      "country": "RU",
      "lat": 55.75,
      "lon": 37.62
    },
    "bio": "Hiking, jazz and good coffee",
    "photos": [
      "https://cdn.example-dating.app/u/123/1.jpg"
    ],
    "photo_verified": true,
    "height_cm": 168
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "consent": {
    "network": true,
    "cross_language": true,
    "world": false,
    "given_at": "2026-10-24T12:00:00Z",
    "text_version": "2026-10-24"
  },
  "display_name": "Anna",
  "birth_year": 1994,
  "gender": "female",
  "age_min": 28,
  "age_max": 40,
  "languages": [
    "ru",
    "en"
  ],
  "city": {
    "name": "Moscow",
    "country": "RU",
    "lat": 55.75,
    "lon": 37.62
  },
  "bio": "Hiking, jazz and good coffee",
  "photos": [
    "https://cdn.example-dating.app/u/123/1.jpg"
  ],
  "photo_verified": true,
  "height_cm": 168
}`)

req, _ := http.NewRequestWithContext(ctx, "PUT", "https://api.pereval.dating/v1/members/user-123", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.

Request body

FieldDescription
consentrequired
object
networkrequired
boolean
The user enabled "show me in the network". false hides the member (see the upsert) instead of being rejected
cross_language
boolean
The user agreed to chat with people speaking other languages (machine translation) · default false
world
boolean
The user wants to meet people from other cities and countries — the scope=world feed. Opt-in only, off by default · default false
given_atrequired
string (date-time)
When the user actually turned it on
text_versionrequired
string
Version of the consent text the user saw, e.g. its date
display_namerequired
string
1–50 chars
birth_yearrequired
integer
Members must be 18+
genderrequired
"male" | "female"
Matches are always of the opposite gender; there is no "seeking" field (ignored if sent)
age_minrequired
integer
≥ 18
age_maxrequired
integer
languagesrequired
string[]
First is the main language: profiles and messages are translated into it. Languages listed here are never translated for this member. Translation languages — GET /v1/languages (38 in v0.8); other ISO 639-1 codes are accepted, names such as Russian are rejected with 422. · ≥ 1 items
cityrequired
object
Name or coordinates required. Can change once a day.
name
string
country
string
ISO 3166-1 alpha-2
lat
number
lon
number
bio
string
≤ 1000 chars
photosrequired
string (uri)[]
Public HTTPS URLs; 1–9 while visible, may be empty while hidden · ≤ 9 items
videos
object[]
Short clips shown on the card next to photos, not instead of them. Omit or send [] to clear. production rejects all nonempty video input with 422 until frame moderation exists. Inspect the videos capability; the following format is for isolated development only. Each clip is an MP4 up to 15 seconds and 20 MB, plus a poster image. The file stays on your host. There is no mute flag: the viewing app starts clips silent and has one switch for every clip. Posters are moderated like photos. A clip that is not an MP4, is longer, or cannot be downloaded rejects the profile (video_invalid, video_too_long, video_unavailable). · ≤ 3 items
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verified
boolean
Before 0.15 — "you verified the person matches the photos". Still accepted: without verification it is a claim with an unspecified method made when the network receives it. Send verification instead. · default false
verification
object
How and when you checked that the person matches their photos. The network turns it into the photo_verified mark that other apps see, if (1) the network has approved your verification method — describe it when you connect, the network reviews it once — and (2) none of the current photos is newer than verified_at. Photos the person joins the network with are covered by an earlier check too; a photo added later switches the mark off until you send a newer verified_at. The same picture under a new URL is recognised by its content. The network re-checks a sample of verified people and those with fake signals (reports, the same photo on another person, generated images); if your marks fail, the network stops counting them for all your users. Omit (and photo_verified: false) — no claim. — or a claim by the network's photo check: {"provider": "pereval", "check_id": "…"}. The network verifies that the check is yours, has passed and covers all current photos (each with a match or no_face verdict, at least one match, recognised by URL or content); then the mark counts even if your own method isn't approved, and verified_at is the time of the check. method and verified_at are then ignored.
method
"selfie" | "video" | "liveness" | "document" | "in_person" | "other"
selfie — a photo selfie (e.g. with a gesture); video — a video selfie; liveness — an automated liveness check; document — an ID with a face match; in_person — met in person
provider
string
Who did the check: a vendor (e.g. Sumsub), own, or pereval — the network's photo check (with check_id) · ≤ 60 chars
verified_at
string (date-time)
When the check passed. Not in the future.
check_id
string (uuid)
id of a passed photo-check session of yours, less than 60 days from original creation. An expired/deleted reference returns 422 invalid; do not retry it or automatically create a replacement check. Omit a stale reference on subsequent upserts. Already accepted verification of unchanged photos is independent of case material retention; expiry does not approve any new photos.
intents
"serious" | "casual" | "friendship" | "arrangement"[]
What the member is looking for. Two members are matched only if they share an intent. arrangement — a relationship with a well-off partner — is opt-in only, requires your app to have the category enabled, and requires arrangement_role. Members whose only shared intent is arrangement are matched only with the opposite role. Offering money for sex is forbidden in every category. · default ["serious","casual","friendship"]
arrangement_role
"provider" | "seeker"
With the arrangement intent: provider — ready to support a partner; seeker — looking for a well-off partner
last_active_at
string (date-time)
height_cm
integer
Height in cm · 120–230
show_distance
boolean
Others may see distance_km to this person. Off by default. Does not change the city. · default false

Responses

200 Stored
FieldDescription
network_idrequired
string (uuid)
The member's id in the network. Stable across updates.
photo_verified
boolean
Whether the verification mark counts: other apps see it and verified_only keeps the person
verification
"accepted" | "none" | "no_photos" | "partner_not_approved" | "photos_changed"
Why the verification mark counts or not: accepted; none — no claim; no_photos; partner_not_approved — the network hasn't approved your verification method yet (never for claims by the network's photo check, ); photos_changed — a photo is newer than verified_at: check the person again and send a newer verified_at.
400 Body is not valid JSON
401 Missing, invalid or suspended API key
409 location_change_limited — the city can change once a day
422 Validation failed; message says which field
429 Over 600 requests per minute

Erase a member from the network

DELETE/v1/members/{external_id}

Call when the user deletes their account or asks to delete their data from the network (to just turn the network off, send consent.network: false — see the upsert). Their open matches are closed: the other platforms get match.closed with reason (account_deleted or left_network) and peer_deleted: true — show the chat as "Account deleted" / "Left the network", read-only. the messages stay with the other person (like Telegram); the profile (name, photos, bio, city), likes, dislikes and blocks are erased. If the user comes back, it is a new member.

curl -X DELETE 'https://api.pereval.dating/v1/members/user-123' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.delete(
    "https://api.pereval.dating/v1/members/user-123",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/members/user-123", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
req, _ := http.NewRequestWithContext(ctx, "DELETE", "https://api.pereval.dating/v1/members/user-123", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.
reason
query · "left_network" | "account_deleted"
account_deleted or left_network (default) — goes to the other side in match.closed

Responses

204 Removed
401 Missing, invalid or suspended API key
404 Member, match or message not found (or not yours)

The user is deleting their account (can restore within 30 days)

POST/v1/members/{external_id}/deactivate

Deferred account deletion: the member disappears from feeds and "liked you", their matches are frozen — the other platforms get match.frozen and should show "Account deleted"; nobody can write (409 match_frozen). Nothing is erased. Call /restore if the user comes back, or DELETE /v1/members/{external_id}?reason=account_deleted when the 30 days are over; if you don't, the network erases the member itself after 30 days.

curl -X POST 'https://api.pereval.dating/v1/members/user-123/deactivate' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/deactivate');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/members/user-123/deactivate",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/deactivate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/members/user-123/deactivate", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.

Responses

204 Deleting (idempotent)
403 Not allowed (e.g. liking or disliking a member of your own app, or a suspended member)
404 Member, match or message not found (or not yours)

Bring back a member the network hid after reports

POST/v1/members/{external_id}/reinstate

Your moderator reviewed the reports (member.reported) and found nothing wrong. The open reports are closed, the member is back in feeds and their frozen matches resume (match.unfrozen to the other platforms). A member who is deleting their account can't be reinstated (403).

curl -X POST 'https://api.pereval.dating/v1/members/user-123/reinstate' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/reinstate');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/members/user-123/reinstate",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/reinstate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/members/user-123/reinstate", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.

Responses

204 Reinstated (idempotent)
403 Not allowed (e.g. liking or disliking a member of your own app, or a suspended member)
404 Member, match or message not found (or not yours)

The user restored their account

POST/v1/members/{external_id}/restore

Everything is back as it was; the frozen matches resume (match.unfrozen to the other platforms).

curl -X POST 'https://api.pereval.dating/v1/members/user-123/restore' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/restore');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/members/user-123/restore",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/restore", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/members/user-123/restore", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.

Responses

204 Restored (idempotent)
403 Not allowed (e.g. liking or disliking a member of your own app, or a suspended member)
404 Member, match or message not found (or not yours)

Mark a member as active now

POST/v1/members/{external_id}/active

Feeds prefer recently active people and hide those inactive for 30+ days. For many users use POST /v1/activity.

curl -X POST 'https://api.pereval.dating/v1/members/user-123/active' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/active');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/members/user-123/active",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/active", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/members/user-123/active", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.

Responses

204 Updated
404 Member, match or message not found (or not yours)

Mark many members as active

POST/v1/activity

Up to 1000 external ids — e.g. everyone who opened the app in the last few minutes. Unknown ids are skipped.

curl -X POST 'https://api.pereval.dating/v1/activity' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "external_ids": [
    "string"
  ]
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/activity');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'external_ids' => ['string'],
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/activity",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "external_ids": [
            "string"
        ]
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/activity", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "external_ids": [
      "string"
    ]
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "external_ids": [
    "string"
  ]
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/activity", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
external_idsrequired
string[]
1–1000 items

Responses

200 How many members were found and updated
FieldDescription
updated
integer
422 Validation failed; message says which field

Feed

People from other apps who fit your user.

People from other apps for this member

GET/v1/members/{external_id}/feed

Members of other platforms only (you show your own users yourself). A candidate appears when: opposite gender; each one's age is inside the other's range; within 50 km or radius_km (or the same city and country when coordinates are missing); they share a language, or both consented to cross-language chat; they share an intent; active in the last 30 days; not liked, not disliked in the last 30 days; no block either way. Most recently active first.

Filters only narrow what the viewer's profile already allows. age_min/age_max intersect the viewer's range (an empty intersection is an empty feed and 200, not an error; age_min > age_max in the query is 422). intent must be one of the viewer's intents, or 422. language is an ISO 639-1 code and still requires a shared language or both consents to cross-language chat. gender, if sent, must be the opposite of the viewer, or 422.

Pages use cursor from the previous response's next_cursor. The same request without cursor returns the same people. A member sees at most 200 distinct new people a day (UTC) — remaining_today. Repeating people already shown today does not spend it. At zero, new people stop and people already shown today still come back. A dislike hides a profile for 30 days; it is not how you turn the page. A member hidden after reports or deleting their account gets no feed (403).

bio is translated into the viewer's main language when the viewer doesn't speak its language — show the "translated" mark and let the user see bio_original. Translations are made on demand: the first time a profile is shown to someone with a new main language it may come in the original (bio_translated: false), and later feeds carry the translation.

scope=world drops the distance condition: people from any city and country, but only those with consent.world. The viewer needs that consent too, otherwise 409 world_consent_required. Language and intent conditions still apply. The 200 a day limit is shared by both scopes. Show the city and country on the card.

Viewer filters: radius_km widens or narrows the nearby search (ignored with scope=world); verified_only=true keeps only photo_verified profiles (— marks the network counts, see verification); height_min/height_max keep people within that height — people who didn't specify their height still pass, so a height filter never empties the feed by itself. Store your users' filters yourself and send them with every request.

lat+lon (and optional city/country) search nearby around that point instead of the viewer's profile city — the profile city does not change. distance_km is present only when that person turned on show_distance.

curl -X GET 'https://api.pereval.dating/v1/members/user-123/feed' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "X-Feed-Request-Ref: $PEREVAL_FEED_REQUEST_REF"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/feed');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'X-Feed-Request-Ref: ' . getenv('PEREVAL_FEED_REQUEST_REF'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/members/user-123/feed",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}", "X-Feed-Request-Ref": os.environ['PEREVAL_FEED_REQUEST_REF']},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/feed", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "X-Feed-Request-Ref": process.env.PEREVAL_FEED_REQUEST_REF,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/members/user-123/feed", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("X-Feed-Request-Ref", os.Getenv("PEREVAL_FEED_REQUEST_REF"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.
X-Feed-Request-Ref
header · string
Logical search reference for opt-in boost delivery; preserve across retries/pages, change for a new search.
limit
query · integer
scope
query · "nearby" | "world"
radius_km
query · integer
Search radius for scope=nearby
verified_only
query · boolean
Only people with photo_verified
height_min
query · integer
Minimum height, cm; people without a height pass
height_max
query · integer
Maximum height, cm, not less than height_min; people without a height pass
lat
query · number
Search centre latitude for scope=nearby; requires lon. Does not change the viewer's city.
lon
query · number
Search centre longitude for scope=nearby; requires lat
city
query · string
Place name for the same-city fallback when a profile has no coordinates
country
query · string
ISO 3166-1 alpha-2 with city
age_min
query · integer
Narrows the viewer's age range. Empty intersection with the profile returns an empty feed, not 422.
age_max
query · integer
Narrows the viewer's age range. Must be ≥ age_min when both are set.
intent
query · "serious" | "casual" | "friendship" | "arrangement"
One intent from the viewer's profile. Candidates must have it too.
language
query · string
ISO 639-1. Candidates must list it. Does not bypass the shared-language rule.
gender
query · "male" | "female"
Must be the opposite of the viewer.
cursor
query · string
Opaque next page, from the previous next_cursor. Invalid cursor is 422.

Responses

200 Candidates
FieldDescription
itemsrequired
object[]
network_idrequired
string (uuid)
partnerrequired
object
id
string (uuid)
name
string
Name of the platform where the person registered.
display_namerequired
string
agerequired
integer
genderrequired
"male" | "female"
cityrequired
string
country
string
ISO 3166-1 alpha-2
distance_km
integer
only if this person turned on show_distance. Kilometres from the search centre (viewer's city, or lat/lon)
languagesrequired
string[]
biorequired
string
In the viewer's main language when the viewer doesn't speak the original
bio_original
string
bio_translatedrequired
boolean
Show a "translated" mark when true
photosrequired
string (uri)[]
videos
object[]
but disabled in the production executable pending frame moderation. Absent when videos capability is false.
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verifiedrequired
boolean
The person's app verified they match the photos, and the network counts it
verified_by
"partner"
Who verified the photos: partner — the person's app, with a method approved by the network
intentsrequired
string[]
arrangement_role
string
Shown only to viewers who have the arrangement intent themselves
height_cm
integer
Absent when the person didn't specify it
super_like
object
This person sent the viewer a super like (feed, incoming likes, like.received)
message
string
In the viewer's main language when they don't speak the original
message_original
string
translated
boolean
lang
string
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )
remaining_todayrequired
integer
How many new people this member can still be shown today (UTC). Repeats do not decrease it.
next_cursor
string
Pass as cursor for the next page. Absent on the last page.
404 Member, match or message not found (or not yours)
409 world_consent_required — scope=world for a member without consent.world; member_hidden — the member is hidden (consent.network: false)
422 Validation failed; message says which field

Likes & matches

Likes and dislikes across apps; a mutual like creates a match that opens chat.

Like a member of another app

POST/v1/likes

The liked member's platform gets like.received. If the like is mutual a match is created and both platforms get match.created. Liking a member of your own app through the network is not allowed (403).

  • You can only like someone your user could see: a block either way, the other environment (test vs live), or a hidden member who hasn't liked your user → 404, as if the profile didn't exist.
  • A repeated like changes nothing and sends no event; upgrading a like to a super like sends like.received again.
  • While your user's profile is in moderation, their likes are stored but the recipient gets like.received only after the profile passes; if it is rejected, shown likes are withdrawn (like.withdrawn, rejected).

Super like: super: true with an optional message (up to 200 characters, no contact details — 422). The recipient sees it right away, before any match: like.received has super: true and the sender's profile carries super_like with the message in the recipient's language; in the feed and in incoming likes such a person comes first. Show it clearly highlighted. When a match happens, the message becomes the first message of the chat (message.created to the recipient's platform). At most 5 super likes a day per member — then 429 super_like_limit; a regular like upgraded to a super like counts too. How many your users get (and whether they pay) is up to you.

curl -X POST 'https://api.pereval.dating/v1/likes' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "X-Action-Ref: $PEREVAL_ACTION_REF" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "super": false,
  "message": "string"
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/likes');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'X-Action-Ref: ' . getenv('PEREVAL_ACTION_REF'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'from' => 'string',
        'to' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
        'super' => false,
        'message' => 'string',
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/likes",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}", "X-Action-Ref": os.environ['PEREVAL_ACTION_REF']},
    json={
        "from": "string",
        "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "super": False,
        "message": "string"
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/likes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "X-Action-Ref": process.env.PEREVAL_ACTION_REF,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "from": "string",
    "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "super": false,
    "message": "string"
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "super": false,
  "message": "string"
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/likes", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("X-Action-Ref", os.Getenv("PEREVAL_ACTION_REF"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

X-Action-Ref
header · string
Required for superlikes after commerce enrollment, including zero-price entitlements. Same reference only for the same action payload.

Request body

FieldDescription
fromrequired
string
Your user's external_id
torequired
string (uuid)
network_id from the feed
super
boolean
default false
message
string
Super like only: shown before the match, becomes the first message after it · ≤ 200 chars

Responses

200 Like stored
FieldDescription
matchedrequired
boolean
match_id
string (uuid)
Present when matched
403 Not allowed (e.g. liking or disliking a member of your own app, or a suspended member)
404 Member, match or message not found (or not yours)
409 member_hidden — a hidden member can only like back someone who liked them, without a super like
422 Validation failed; message says which field
429 super_like_limit — more than 5 super likes in 24 h

Take a like (or super like) back before it became a match

DELETE/v1/likes/{network_id}

The liked member's platform gets like.withdrawn. After a match — 409 matched; close the match instead.

curl -X DELETE 'https://api.pereval.dating/v1/likes/{network_id}?from=VALUE' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "X-Action-Ref: $PEREVAL_ACTION_REF"
<?php
$ch = curl_init('https://api.pereval.dating/v1/likes/{network_id}?from=VALUE');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'X-Action-Ref: ' . getenv('PEREVAL_ACTION_REF'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.delete(
    "https://api.pereval.dating/v1/likes/{network_id}?from=VALUE",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}", "X-Action-Ref": os.environ['PEREVAL_ACTION_REF']},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/likes/{network_id}?from=VALUE", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "X-Action-Ref": process.env.PEREVAL_ACTION_REF,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
req, _ := http.NewRequestWithContext(ctx, "DELETE", "https://api.pereval.dating/v1/likes/{network_id}?from=VALUE", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("X-Action-Ref", os.Getenv("PEREVAL_ACTION_REF"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

X-Action-Ref
header · string
Required after commerce enrollment. Replays of the same undo must use the same reference.
network_idrequired
path · string (uuid)
fromrequired
query · string
Your user's external_id

Responses

204 Like removed
404 Member, match or message not found (or not yours)
409 matched — the like already became a match

Bring a skipped profile back

DELETE/v1/dislikes/{network_id}

Removes the dislike — the profile may appear in the feed again.

curl -X DELETE 'https://api.pereval.dating/v1/dislikes/{network_id}?from=VALUE' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "X-Action-Ref: $PEREVAL_ACTION_REF"
<?php
$ch = curl_init('https://api.pereval.dating/v1/dislikes/{network_id}?from=VALUE');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'X-Action-Ref: ' . getenv('PEREVAL_ACTION_REF'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.delete(
    "https://api.pereval.dating/v1/dislikes/{network_id}?from=VALUE",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}", "X-Action-Ref": os.environ['PEREVAL_ACTION_REF']},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/dislikes/{network_id}?from=VALUE", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "X-Action-Ref": process.env.PEREVAL_ACTION_REF,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
req, _ := http.NewRequestWithContext(ctx, "DELETE", "https://api.pereval.dating/v1/dislikes/{network_id}?from=VALUE", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("X-Action-Ref", os.Getenv("PEREVAL_ACTION_REF"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

X-Action-Ref
header · string
Required after commerce enrollment. Replays of the same undo must use the same reference.
network_idrequired
path · string (uuid)
fromrequired
query · string
Your user's external_id

Responses

204 Dislike removed
404 Member, match or message not found (or not yours)

Your user saw a profile and didn't like it

POST/v1/dislikes

Send it when your user swiped left or scrolled past a profile without liking. Only your app knows what the user actually saw, so the network never treats a served feed as seen. The profile leaves the user's feed for 30 days. One pair or a batch of up to 100.

curl -X POST 'https://api.pereval.dating/v1/dislikes' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "erase": false
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/dislikes');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'from' => 'string',
        'to' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
        'erase' => false,
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/dislikes",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "from": "string",
        "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "erase": False
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/dislikes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "from": "string",
    "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "erase": false
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "erase": false
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/dislikes", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
fromrequired
string
Your user's external_id
torequired
string (uuid)
network_id from the feed
erase
boolean
blocks only: delete the match, messages and likes for both · default false

Responses

204 Stored
403 Not allowed (e.g. liking or disliking a member of your own app, or a suspended member)
404 Member, match or message not found (or not yours)

Who liked this member and waits for an answer

GET/v1/members/{external_id}/likes

Without mutual likes, dislikes and blocks. Rebuilds "Liked you" when events were lost.

curl -X GET 'https://api.pereval.dating/v1/members/user-123/likes' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/likes');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/members/user-123/likes",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/likes", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/members/user-123/likes", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.
limit
query · integer

Responses

200 Incoming likes, newest first
FieldDescription
itemsrequired
object[]
fromrequired
object
A member of another app as seen by your user.
network_idrequired
string (uuid)
partnerrequired
object
id
string (uuid)
name
string
Name of the platform where the person registered.
display_namerequired
string
agerequired
integer
genderrequired
"male" | "female"
cityrequired
string
country
string
ISO 3166-1 alpha-2
distance_km
integer
only if this person turned on show_distance. Kilometres from the search centre (viewer's city, or lat/lon)
languagesrequired
string[]
biorequired
string
In the viewer's main language when the viewer doesn't speak the original
bio_original
string
bio_translatedrequired
boolean
Show a "translated" mark when true
photosrequired
string (uri)[]
videos
object[]
but disabled in the production executable pending frame moderation. Absent when videos capability is false.
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verifiedrequired
boolean
The person's app verified they match the photos, and the network counts it
verified_by
"partner"
Who verified the photos: partner — the person's app, with a method approved by the network
intentsrequired
string[]
arrangement_role
string
Shown only to viewers who have the arrangement intent themselves
height_cm
integer
Absent when the person didn't specify it
super_like
object
This person sent the viewer a super like (feed, incoming likes, like.received)
message
string
In the viewer's main language when they don't speak the original
message_original
string
translated
boolean
lang
string
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )
liked_atrequired
string (date-time)
404 Member, match or message not found (or not yours)

Open matches of a member

GET/v1/members/{external_id}/matches

curl -X GET 'https://api.pereval.dating/v1/members/user-123/matches' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/matches');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/members/user-123/matches",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/matches", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/members/user-123/matches", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string
Your user id.

Responses

200 Matches, newest first
FieldDescription
itemsrequired
object[]
match_idrequired
string (uuid)
created_atrequired
string (date-time)
withrequired
object
A member of another app as seen by your user.
network_idrequired
string (uuid)
partnerrequired
object
id
string (uuid)
name
string
Name of the platform where the person registered.
display_namerequired
string
agerequired
integer
genderrequired
"male" | "female"
cityrequired
string
country
string
ISO 3166-1 alpha-2
distance_km
integer
only if this person turned on show_distance. Kilometres from the search centre (viewer's city, or lat/lon)
languagesrequired
string[]
biorequired
string
In the viewer's main language when the viewer doesn't speak the original
bio_original
string
bio_translatedrequired
boolean
Show a "translated" mark when true
photosrequired
string (uri)[]
videos
object[]
but disabled in the production executable pending frame moderation. Absent when videos capability is false.
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verifiedrequired
boolean
The person's app verified they match the photos, and the network counts it
verified_by
"partner"
Who verified the photos: partner — the person's app, with a method approved by the network
intentsrequired
string[]
arrangement_role
string
Shown only to viewers who have the arrangement intent themselves
height_cm
integer
Absent when the person didn't specify it
super_like
object
This person sent the viewer a super like (feed, incoming likes, like.received)
message
string
In the viewer's main language when they don't speak the original
message_original
string
translated
boolean
lang
string
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )
frozen
boolean
Omitted when false
freeze_reason
"account_deleting" | "suspended" | "partner_suspended"
Present when frozen

Close or erase a match (unmatch)

DELETE/v1/matches/{match_id}

Without erase the match is closed and both keep the history (match.closed, reason: unmatched). With erase=true the match, its messages and both likes are deleted for both — the other platform gets match.erased and should remove the chat; the two won't meet in each other's feeds for cooldown_days. If either of them reported the other, a hidden copy of the chat is kept for moderation for 90 days. To hide the history only for your user, use POST /v1/matches/{match_id}/clear.

curl -X DELETE 'https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10?by=user-123' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10?by=user-123');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.delete(
    "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10?by=user-123",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10?by=user-123", {
  method: "DELETE",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
req, _ := http.NewRequestWithContext(ctx, "DELETE", "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10?by=user-123", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

match_idrequired
path · string (uuid)
byrequired
query · string
external_id of your user who unmatches
erase
query · boolean
Delete the match, messages and likes for both
cooldown_days
query · integer
with erase. Days before they can see each other in the feed again; for "never" block instead

Responses

204 Closed (match.closed) or erased (match.erased)
404 Member, match or message not found (or not yours)
422 Validation failed; message says which field

Messages

Text chat inside a match, machine-translated when the recipient doesn't speak the sender's language.

Delete the chat history for one side

POST/v1/matches/{match_id}/clear

"Delete chat" for your user only: the history before now is no longer returned to them; the other person keeps everything, the match stays, and new messages go both ways as usual.

curl -X POST 'https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/clear' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "as": "string"
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/clear');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'as' => 'string',
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/clear",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "as": "string"
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/clear", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "as": "string"
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "as": "string"
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/clear", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

match_idrequired
path · string (uuid)

Request body

FieldDescription
asrequired
string
external_id of your user

Responses

204 Cleared
404 Member, match or message not found (or not yours)

Conversation history as seen by a member

GET/v1/matches/{match_id}/messages

Newest first by (created_at, id). Messages from the other side are in the viewer's language with the original and scam flags; own messages carry read_at and, when supplied, client_message_id for retry reconciliation. The before message must belong to this match. Closure preserves history; erasure removes it.

curl -X GET 'https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages?as=user-123' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages?as=user-123');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages?as=user-123",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages?as=user-123", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages?as=user-123", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

match_idrequired
path · string (uuid)
asrequired
query · string
external_id of the viewer
before
query · string (uuid)
Message id — returns older messages (pagination cursor)
limit
query · integer

Responses

200 Messages
FieldDescription
itemsrequired
object[]
idrequired
string (uuid)
fromrequired
string
"me" for the viewer's own messages, otherwise the sender's network_id
client_message_id
string
Author history/POST echo when supplied; also included in message.created for reconciliation
textrequired
string
text_original
string
translatedrequired
boolean
langrequired
string
created_atrequired
string (date-time)
suspicious
boolean
Messages from the other side only
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )
read_at
string (date-time)
Own messages only: when the other side read it
404 Member, match or message not found (or not yours)

Send a message

POST/v1/matches/{match_id}/messages

The network detects the language from the text; short replies ("ok", "haha", emoji) are attributed to one of the sender's languages, the main one first. If the recipient doesn't speak it, the message is translated in the background into the recipient's main language, and the recipient's platform gets message.created once the translation is ready. If translation fails the message is still delivered in the original language (translated: false).

Cheap scam signals are checked: links, phone numbers and invitations to other messengers early in a chat, talk about money, the same text sent to many people. The message is delivered anyway, with suspicious and flags, so the recipient's app can show a warning. Members in their first 7 days can start at most 20 conversations a day (429 first_messages_limit); replying in existing conversations is unlimited.

optional client_message_id is sender-scoped (not match-scoped), at most 128 UTF-8 bytes, without surrounding whitespace. Repeating the same ID, match and trimmed text returns the original message, including after match closure; changing match/text returns 409 idempotency_conflict. Ownership/auth are checked before replay. A suspended platform key is never authorized by a replay. After message retention or explicit match erasure, the original is no longer available: this is not a permanent idempotency reservation. Persist the returned message ID; recover a timed-out response through author history.

curl -X POST 'https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "string",
  "text": "string",
  "client_message_id": "string"
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'from' => 'string',
        'text' => 'string',
        'client_message_id' => 'string',
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "from": "string",
        "text": "string",
        "client_message_id": "string"
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "from": "string",
    "text": "string",
    "client_message_id": "string"
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "from": "string",
  "text": "string",
  "client_message_id": "string"
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/messages", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

match_idrequired
path · string (uuid)

Request body

FieldDescription
fromrequired
string
Sender's external_id
textrequired
string
1–4000 chars
client_message_id
string
Optional sender-scoped retry ID; maximum 128 UTF-8 bytes, no surrounding whitespace · 1–128 chars

Responses

200 Sent
FieldDescription
idrequired
string (uuid)
fromrequired
string
"me" for the viewer's own messages, otherwise the sender's network_id
client_message_id
string
Author history/POST echo when supplied; also included in message.created for reconciliation
textrequired
string
text_original
string
translatedrequired
boolean
langrequired
string
created_atrequired
string (date-time)
suspicious
boolean
Messages from the other side only
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )
read_at
string (date-time)
Own messages only: when the other side read it
404 Member, match or message not found (or not yours)
409 match_closed, match_frozen (including partner suspension), or idempotency_conflict
422 Validation failed; message says which field
429 first_messages_limit — a new member started too many conversations today, or rate_limited

Your user read the conversation

POST/v1/matches/{match_id}/read

Marks the other side's messages up to up_to (inclusive) as read; the other platform gets message.read.

curl -X POST 'https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/read' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "as": "string",
  "up_to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/read');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'as' => 'string',
        'up_to' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/read",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "as": "string",
        "up_to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/read", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "as": "string",
    "up_to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "as": "string",
  "up_to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44"
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/matches/7d2a0f4e-1b6c-4e8a-9f3d-5c1b2a7e9d10/read", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

match_idrequired
path · string (uuid)

Request body

FieldDescription
asrequired
string
external_id of the reader
up_torequired
string (uuid)
Id of the last message read

Responses

204 Marked
404 Member, match or message not found (or not yours)

Safety

Blocks and reports. Reports reach the reported member's platform; serious ones suspend the member network-wide.

Block a member

POST/v1/blocks

Both disappear from each other's feeds for good. Without erase an open match between them is closed (match.closed, reason: blocked), the history stays. With erase: true the match, messages and likes are deleted for both (match.erased, reason: blocked); if either reported the other, a hidden copy is kept for moderation for 90 days.

curl -X POST 'https://api.pereval.dating/v1/blocks' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "erase": false
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/blocks');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'from' => 'string',
        'to' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
        'erase' => false,
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/blocks",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "from": "string",
        "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "erase": False
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/blocks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "from": "string",
    "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "erase": false
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "erase": false
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/blocks", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
fromrequired
string
Your user's external_id
torequired
string (uuid)
network_id from the feed
erase
boolean
blocks only: delete the match, messages and likes for both · default false

Responses

204 Blocked
404 Member, match or message not found (or not yours)

Report a member

POST/v1/reports

The reported member's platform gets member.reported. A report with reason: underage or commercial, or reports from 3 different people, suspends the member from the network until review (member.suspended): they are out of feeds and their matches are frozen (match.frozen with reason: suspended, 409 match_frozen for both). Their platform brings them back with POST /v1/members/{external_id}/reinstate.

curl -X POST 'https://api.pereval.dating/v1/reports' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "reason": "fake",
  "comment": "string"
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/reports');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'from' => 'string',
        'to' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
        'reason' => 'fake',
        'comment' => 'string',
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/reports",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "from": "string",
        "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "reason": "fake",
        "comment": "string"
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/reports", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "from": "string",
    "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "reason": "fake",
    "comment": "string"
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "from": "string",
  "to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "reason": "fake",
  "comment": "string"
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/reports", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
fromrequired
string
torequired
string (uuid)
reasonrequired
"fake" | "scam" | "spam" | "offensive" | "underage" | "commercial" | "other"
commercial — offers sex for money; suspends the member at once
comment
string
≤ 1000 chars

Responses

204 Reported
404 Member, match or message not found (or not yours)
422 Validation failed; message says which field

Languages

Languages the network translates profiles and messages into.

Translation languages

GET/v1/languages

ISO 639-1 codes the network translates between. Members may list other ISO 639-1 codes in languages, but a member whose main language is not in this list reads others in the original.

curl -X GET 'https://api.pereval.dating/v1/languages' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/languages');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/languages",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/languages", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/languages", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Responses

200 Languages
FieldDescription
translatedrequired
string[]
ISO 639-1 codes
401 Missing, invalid or suspended API key

Photo check

The network's photo check for platforms without their own: the person takes it on a network page (consent, a requested gesture, a camera picture); the network compares the picture with each profile photo.

Start a photo check

POST/v1/photo-checks

Creates a check session with the person's profile photos (the network downloads them right away) and returns the url of the network's check page. Open it for the person — in the browser, a new tab or a webview with camera access; it can't be embedded in an iframe. The link is valid for 30 minutes and allows up to 5 attempts. On the page the person agrees to the comparison, shows the requested gesture and takes a picture with the camera; the network compares it with each photo. The result comes as photo_check.updated (after each attempt, a dispute and the moderator's decision) and in GET /v1/photo-checks/{id}; the page also posts {type: "pereval.photo_check", status} to the window that opened it.

The picture is never shown to you or other people and no facial features are stored: it is deleted right after a passed check, kept for 24 hours after a failed one (so the person can dispute the result) and until the moderator's decision if disputed, but never beyond 60 days in the active database from original check creation, including unresolved disputes. Personal metadata, references and source photos are also subject to that absolute limit. Updates, appeals and disputes do not restart it; expiry never approves a photo or removes a sanction. Operators must review before expiry. Public access ends at the earlier public token expiry independently of retained evidence. The consent names your platform and the network, which compares photos on your behalf.

curl -X POST 'https://api.pereval.dating/v1/photo-checks' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ref": "u_123",
  "lang": "en",
  "return_url": "https://your.app/verified",
  "photos": [
    {
      "id": "ph1",
      "url": "https://cdn.your.app/ph1.jpg"
    },
    {
      "id": "ph2",
      "url": "https://cdn.your.app/ph2.jpg"
    }
  ]
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/photo-checks');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'ref' => 'u_123',
        'lang' => 'en',
        'return_url' => 'https://your.app/verified',
        'photos' => [[
            'id' => 'ph1',
            'url' => 'https://cdn.your.app/ph1.jpg',
        ], [
            'id' => 'ph2',
            'url' => 'https://cdn.your.app/ph2.jpg',
        ]],
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/photo-checks",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "ref": "u_123",
        "lang": "en",
        "return_url": "https://your.app/verified",
        "photos": [
            {
                "id": "ph1",
                "url": "https://cdn.your.app/ph1.jpg"
            },
            {
                "id": "ph2",
                "url": "https://cdn.your.app/ph2.jpg"
            }
        ]
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/photo-checks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "ref": "u_123",
    "lang": "en",
    "return_url": "https://your.app/verified",
    "photos": [
      {
        "id": "ph1",
        "url": "https://cdn.your.app/ph1.jpg"
      },
      {
        "id": "ph2",
        "url": "https://cdn.your.app/ph2.jpg"
      }
    ]
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "ref": "u_123",
  "lang": "en",
  "return_url": "https://your.app/verified",
  "photos": [
    {
      "id": "ph1",
      "url": "https://cdn.your.app/ph1.jpg"
    },
    {
      "id": "ph2",
      "url": "https://cdn.your.app/ph2.jpg"
    }
  ]
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/photo-checks", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
refrequired
string
How you recognise the person in the result, usually their external_id · 1–200 chars
photosrequired
object[]
1–9 items
id
string
Your photo id, unique in the session; default — the position (1, 2, …) · ≤ 100 chars
urlrequired
string (uri)
Public HTTPS URL of a profile photo
lang
"ru" | "en"
Language of the check page · default "en"
return_url
string (uri)
Optional: the page's "Back" button · ≤ 500 chars

Responses

201 Session created — open url for the person
FieldDescription
idrequired
string (uuid)
refrequired
string
url
string (uri)
Only in the POST reply: the check page to open for the person
statusrequired
"created" | "passed" | "partial" | "failed" | "disputed" | "resolved" | "expired"
created — not taken yet; passed — it's the person on every photo with a face; partial — some photos don't look like the picture; failed — see reason; disputed — the person disputed, a network moderator decides (usually within 24 hours); resolved — the moderator decided (note explains); expired — the link expired untaken.
reason
"no_match" | "no_face" | "many_faces" | "no_gesture" | "virtual_camera"
Why it failed
attemptsrequired
integer
Pictures taken (up to 5 per session)
expires_atrequired
string (date-time)
Until when the link allows attempts
checked_at
string (date-time)
The last picture
dispute_until
string (date-time)
Failed or partial: the person can dispute until then
disputed_at
string (date-time)
decided_at
string (date-time)
The moderator's decision
note
string
The moderator's explanation for the person
photosrequired
object[]
idrequired
string
urlrequired
string (uri)
verdict
"match" | "no_match" | "unsure" | "no_face"
match — the same person; no_match — doesn't look like them; unsure — couldn't compare reliably; no_face — no face, not counted. Absent — not compared yet
created_atrequired
string (date-time)
401 Missing, invalid or suspended API key
422 Validation failed; message says which field
429 photo_check_limit — more than 10 checks for this person (ref) a day; or rate_limited
503 photo_check_off — the comparison service is temporarily unavailable; retry later

Photo check result

GET/v1/photo-checks/{id}

curl -X GET 'https://api.pereval.dating/v1/photo-checks/{id}' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/photo-checks/{id}');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/photo-checks/{id}",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/photo-checks/{id}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/photo-checks/{id}", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

idrequired
path · string (uuid)

Responses

200 The check (without url)
FieldDescription
idrequired
string (uuid)
refrequired
string
url
string (uri)
Only in the POST reply: the check page to open for the person
statusrequired
"created" | "passed" | "partial" | "failed" | "disputed" | "resolved" | "expired"
created — not taken yet; passed — it's the person on every photo with a face; partial — some photos don't look like the picture; failed — see reason; disputed — the person disputed, a network moderator decides (usually within 24 hours); resolved — the moderator decided (note explains); expired — the link expired untaken.
reason
"no_match" | "no_face" | "many_faces" | "no_gesture" | "virtual_camera"
Why it failed
attemptsrequired
integer
Pictures taken (up to 5 per session)
expires_atrequired
string (date-time)
Until when the link allows attempts
checked_at
string (date-time)
The last picture
dispute_until
string (date-time)
Failed or partial: the person can dispute until then
disputed_at
string (date-time)
decided_at
string (date-time)
The moderator's decision
note
string
The moderator's explanation for the person
photosrequired
object[]
idrequired
string
urlrequired
string (uri)
verdict
"match" | "no_match" | "unsure" | "no_face"
match — the same person; no_match — doesn't look like them; unsure — couldn't compare reliably; no_face — no face, not counted. Absent — not compared yet
created_atrequired
string (date-time)
401 Missing, invalid or suspended API key
404 Member, match or message not found (or not yours)

Events

Pull events yourself — for platforms without a webhook URL and for recovery after an outage.

Pull events

GET/v1/events

Events of your environment in seq order after the given cursor; pulled events count as delivered. If your platform has no webhook URL, the network doesn't push — you pull. Events are kept for 7 days. The data of each event is the same as in the webhook of that type.

curl -X GET 'https://api.pereval.dating/v1/events' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/events');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/events",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/events", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/events", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

after
query · integer
next_after from the previous response (0 — from the oldest kept event)
limit
query · integer

Responses

200 Events
FieldDescription
itemsrequired
object[]
seqrequired
integer
idrequired
string
typerequired
string
created_atrequired
string (date-time)
datarequired
object
next_afterrequired
integer
Pass as after next time

MCP

Model Context Protocol JSON-RPC 2.0 for AI agents. Same Bearer key as REST. POST /mcp — methods initialize, tools/list, tools/call, ping. Protocol version 2024-11-05.

Model Context Protocol (JSON-RPC)

POST/mcp

Same API key as the REST API. Methods: initialize, tools/list, tools/call, ping. Tools: upsert_member, get_feed, like, dislike, send_message, list_events, list_languages. A tools/call result is JSON in result.content[].text. RPC errors use JSON-RPC error; tool failures set isError: true and keep HTTP 200. Missing key — HTTP 401.

curl -X POST 'https://api.pereval.dating/mcp' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "jsonrpc": "2.0",
  "id": {},
  "method": "tools/list",
  "params": {}
}'
<?php
$ch = curl_init('https://api.pereval.dating/mcp');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'jsonrpc' => '2.0',
        'id' => [

        ],
        'method' => 'tools/list',
        'params' => [

        ],
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/mcp",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "jsonrpc": "2.0",
        "id": {},
        "method": "tools/list",
        "params": {}
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/mcp", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "jsonrpc": "2.0",
    "id": {},
    "method": "tools/list",
    "params": {}
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
body := []byte(`{
  "jsonrpc": "2.0",
  "id": {},
  "method": "tools/list",
  "params": {}
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/mcp", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
jsonrpcrequired
"2.0"
id
object
number, string or null (null = notification, no response)
methodrequired
string
params
object

Responses

200 JSON-RPC result or error
401 Missing or invalid API key

Commerce

Current own boost and remaining time; voluntary cancellation is not supported

GET/v1/members/{external_id}/boost

curl -X GET 'https://api.pereval.dating/v1/members/user-123/boost' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/boost');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/members/user-123/boost",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/boost", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/members/user-123/boost", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string

Responses

200 Boost state
FieldDescription
generationrequired
string (uuid)
staterequired
"active" | "paused" | "completed"
started_atrequired
string (date-time)
remaining_msrequired
integer
≥ 0
api_placementsrequired
integer
≥ 0
404 Own boost not found
503 Pilot unavailable

Declare receiving support independently of purchases

PUT/v1/commerce/delivery

curl -X PUT 'https://api.pereval.dating/v1/commerce/delivery' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "format": "swipe",
  "enabled": true
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/commerce/delivery');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'format' => 'swipe',
        'enabled' => true,
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.put(
    "https://api.pereval.dating/v1/commerce/delivery",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "format": "swipe",
        "enabled": True
    },
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/v1/commerce/delivery", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "format": "swipe",
    "enabled": true
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
body := []byte(`{
  "format": "swipe",
  "enabled": true
}`)

req, _ := http.NewRequestWithContext(ctx, "PUT", "https://api.pereval.dating/v1/commerce/delivery", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
formatrequired
"swipe" | "search" | "none"
enabledrequired
boolean

Responses

200 Configured format version 1
422 Unsupported/inconsistent format; map receiving is not implemented
503 Pilot unavailable

Catalog in the enrolled account currency

GET/v1/commerce/catalog

curl -X GET 'https://api.pereval.dating/v1/commerce/catalog' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/commerce/catalog');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/commerce/catalog",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/commerce/catalog", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/commerce/catalog", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Responses

200 Success
FieldDescription
currencyrequired
"TEST" | "RUB"
test_onlyrequired
boolean
itemsrequired
object[]
skurequired
string
capabilityrequired
"superlike" | "rewind" | "incognito" | "boost"
unitrequired
"action" | "minute" | "day"
unit_pricerequired
integer (int64)
≥ 0
min_quantityrequired
integer
≥ 1
max_quantityrequired
integer
≥ 1
fulfillment_readyrequired
boolean
versionrequired
integer
≥ 1
currencyrequired
"TEST" | "RUB"
401 See commerce-pilot.md for error codes
402 See commerce-pilot.md for error codes
403 See commerce-pilot.md for error codes
404 See commerce-pilot.md for error codes
409 See commerce-pilot.md for error codes
422 See commerce-pilot.md for error codes
429 See commerce-pilot.md for error codes
503 See commerce-pilot.md for error codes

TEST credits or zero-only RUB balance

GET/v1/commerce/balance

curl -X GET 'https://api.pereval.dating/v1/commerce/balance' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/commerce/balance');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/commerce/balance",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/commerce/balance", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/commerce/balance", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Responses

200 Success
FieldDescription
currencyrequired
"TEST" | "RUB"
test_onlyrequired
boolean
balancerequired
integer (int64)
≥ 0
401 See commerce-pilot.md for error codes
402 See commerce-pilot.md for error codes
403 See commerce-pilot.md for error codes
404 See commerce-pilot.md for error codes
409 See commerce-pilot.md for error codes
422 See commerce-pilot.md for error codes
429 See commerce-pilot.md for error codes
503 See commerce-pilot.md for error codes

Create a five-minute price snapshot; no debit

POST/v1/commerce/quotes

curl -X POST 'https://api.pereval.dating/v1/commerce/quotes' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "member": "string",
  "sku": "string",
  "quantity": 1
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/commerce/quotes');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'member' => 'string',
        'sku' => 'string',
        'quantity' => 1,
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/commerce/quotes",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "member": "string",
        "sku": "string",
        "quantity": 1
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/commerce/quotes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "member": "string",
    "sku": "string",
    "quantity": 1
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "member": "string",
  "sku": "string",
  "quantity": 1
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/commerce/quotes", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
memberrequired
string
1–256 chars
skurequired
string
1–80 chars
quantityrequired
integer
1–1000000

Responses

200 Success
FieldDescription
idrequired
string (uuid)
member_idrequired
string (uuid)
skurequired
string
capabilityrequired
string
unitrequired
string
quantityrequired
integer
≥ 1
unit_pricerequired
integer (int64)
≥ 0
totalrequired
integer (int64)
≥ 0
catalog_versionrequired
integer
expires_atrequired
string (date-time)
currencyrequired
"TEST" | "RUB"
401 See commerce-pilot.md for error codes
402 See commerce-pilot.md for error codes
403 See commerce-pilot.md for error codes
404 See commerce-pilot.md for error codes
409 See commerce-pilot.md for error codes
422 See commerce-pilot.md for error codes
429 See commerce-pilot.md for error codes
503 See commerce-pilot.md for error codes

Atomically purchase entitlement; durable replay by external_order_ref

POST/v1/commerce/orders

curl -X POST 'https://api.pereval.dating/v1/commerce/orders' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "X-Commerce-Key: $PEREVAL_COMMERCE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "quote_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "external_order_ref": "string"
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/commerce/orders');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'X-Commerce-Key: ' . getenv('PEREVAL_COMMERCE_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'quote_id' => '6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44',
        'external_order_ref' => 'string',
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.post(
    "https://api.pereval.dating/v1/commerce/orders",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}", "X-Commerce-Key": os.environ['PEREVAL_COMMERCE_KEY']},
    json={
        "quote_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "external_order_ref": "string"
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/commerce/orders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "X-Commerce-Key": process.env.PEREVAL_COMMERCE_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "quote_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "external_order_ref": "string"
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "quote_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "external_order_ref": "string"
}`)

req, _ := http.NewRequestWithContext(ctx, "POST", "https://api.pereval.dating/v1/commerce/orders", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("X-Commerce-Key", os.Getenv("PEREVAL_COMMERCE_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Request body

FieldDescription
quote_idrequired
string (uuid)
external_order_refrequired
string
1–128 chars

Responses

200 Success
FieldDescription
idrequired
string (uuid)
external_order_refrequired
string
quote_idrequired
string (uuid)
totalrequired
integer (int64)
≥ 0
created_atrequired
string (date-time)
currencyrequired
"TEST" | "RUB"
grant_idrequired
string (uuid)
valid_fromrequired
string (date-time)
valid_untilrequired
string,null (date-time)
401 See commerce-pilot.md for error codes
402 See commerce-pilot.md for error codes
403 See commerce-pilot.md for error codes
404 See commerce-pilot.md for error codes
409 See commerce-pilot.md for error codes
422 See commerce-pilot.md for error codes
429 See commerce-pilot.md for error codes
503 See commerce-pilot.md for error codes

List own subject entitlements

GET/v1/members/{external_id}/entitlements

curl -X GET 'https://api.pereval.dating/v1/members/user-123/entitlements' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/entitlements');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/members/user-123/entitlements",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/entitlements", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/members/user-123/entitlements", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string

Responses

200 Success
FieldDescription
itemsrequired
object[]
idrequired
string (uuid)
capabilityrequired
string
quantityrequired
integer
≥ 1
usedrequired
integer (int64)
≥ 0
valid_fromrequired
string (date-time)
valid_untilrequired
string,null (date-time)
revoked_atrequired
string,null (date-time)
401 See commerce-pilot.md for error codes
402 See commerce-pilot.md for error codes
403 See commerce-pilot.md for error codes
404 See commerce-pilot.md for error codes
409 See commerce-pilot.md for error codes
422 See commerce-pilot.md for error codes
429 See commerce-pilot.md for error codes
503 See commerce-pilot.md for error codes

Explicitly enable incognito with a valid right or freely disable it

PUT/v1/members/{external_id}/incognito

curl -X PUT 'https://api.pereval.dating/v1/members/user-123/incognito' \
  -H "Authorization: Bearer $PEREVAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true
}'
<?php
$ch = curl_init('https://api.pereval.dating/v1/members/user-123/incognito');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'PUT',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'enabled' => true,
    ], JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.put(
    "https://api.pereval.dating/v1/members/user-123/incognito",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    json={
        "enabled": True
    },
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/members/user-123/incognito", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "enabled": true
  }),
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
body := []byte(`{
  "enabled": true
}`)

req, _ := http.NewRequestWithContext(ctx, "PUT", "https://api.pereval.dating/v1/members/user-123/incognito", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Parameters

external_idrequired
path · string

Request body

FieldDescription
enabledrequired
boolean

Responses

200 Success
FieldDescription
enabledrequired
boolean
401 See commerce-pilot.md for error codes
402 See commerce-pilot.md for error codes
403 See commerce-pilot.md for error codes
404 See commerce-pilot.md for error codes
409 See commerce-pilot.md for error codes
422 See commerce-pilot.md for error codes
429 See commerce-pilot.md for error codes
503 See commerce-pilot.md for error codes

Other

Process liveness

GET/healthz

curl -X GET 'https://api.pereval.dating/healthz' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/healthz');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/healthz",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/healthz", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/healthz", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Responses

200 Process is running

Database readiness, not a check of external moderation or AI availability

GET/readyz

curl -X GET 'https://api.pereval.dating/readyz' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/readyz');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/readyz",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
const res = await fetch("https://api.pereval.dating/readyz", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/readyz", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Responses

200 Database responds within two seconds
503 Database unavailable (not_ready)

Runtime protocol and feature configuration

GET/v1/capabilities

curl -X GET 'https://api.pereval.dating/v1/capabilities' \
  -H "Authorization: Bearer $PEREVAL_API_KEY"
<?php
$ch = curl_init('https://api.pereval.dating/v1/capabilities');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PEREVAL_API_KEY'),
    ],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status >= 400) {
    throw new RuntimeException("Pereval $status: $response");
}
$data = json_decode($response, true);
import os, requests

resp = requests.get(
    "https://api.pereval.dating/v1/capabilities",
    headers={"Authorization": f"Bearer {os.environ['PEREVAL_API_KEY']}"},
    timeout=20,
)
resp.raise_for_status()
data = resp.json()
const res = await fetch("https://api.pereval.dating/v1/capabilities", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.PEREVAL_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Pereval ${res.status}: ${await res.text()}`);
const data = await res.json();
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.pereval.dating/v1/capabilities", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("PEREVAL_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
	return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
	b, _ := io.ReadAll(resp.Body)

	return fmt.Errorf("pereval %d: %s", resp.StatusCode, b)
}

Responses

200 Configuration, not proof that external dependencies are healthy
FieldDescription
protocol_versionrequired
"1.0.0"
productionrequired
boolean
commerce_enabledrequired
boolean
Full network commerce is enabled; does not imply this partner is enrolled
commerce_live_zero_onlyrequired
boolean
Live checkout accepts zero RUB only; no monetary payment processor
commerce_pilotrequired
boolean
TEST-only development pilot; false in production
client_message_idrequired
true
videosrequired
boolean
False in the production executable
photo_checksrequired
boolean
Facecheck URL is configured
profile_moderationrequired
boolean
Profile moderation is configured
401 Missing

Events (webhooks)

Every webhook is a POST with this envelope. Headers: X-Network-Signature: sha256=<hex> — HMAC-SHA256 of the raw body with your signing secret (verify it, constant-time compare); X-Network-Event: <type>. Reply 2xx quickly. Non-2xx or timeout (10 s) → retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h, then daily for up to 3 days. Deliveries may repeat — dedupe by id. Order holds unless a delivery is retried. Without a webhook URL, pull the same events with GET /v1/events.

Signature checks and handling — see Webhooks

boost.started

Boost started

Durable boost lifecycle event, not a human impression. Reconcile current state with the member boost endpoint.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "boost.started",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "generation": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "remaining_ms": 0
  }
}

data

FieldDescription
memberrequired
string
Your member external_id
generationrequired
string (uuid)
remaining_msrequired
integer
≥ 0

boost.extended

Boost extended

Durable boost lifecycle event, not a human impression. Reconcile current state with the member boost endpoint.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "boost.extended",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "generation": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "remaining_ms": 0
  }
}

data

FieldDescription
memberrequired
string
Your member external_id
generationrequired
string (uuid)
remaining_msrequired
integer
≥ 0

boost.paused

Boost paused

Durable boost lifecycle event, not a human impression. Reconcile current state with the member boost endpoint.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "boost.paused",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "generation": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "remaining_ms": 0
  }
}

data

FieldDescription
memberrequired
string
Your member external_id
generationrequired
string (uuid)
remaining_msrequired
integer
≥ 0

boost.resumed

Boost resumed

Durable boost lifecycle event, not a human impression. Reconcile current state with the member boost endpoint.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "boost.resumed",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "generation": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "remaining_ms": 0
  }
}

data

FieldDescription
memberrequired
string
Your member external_id
generationrequired
string (uuid)
remaining_msrequired
integer
≥ 0

boost.completed

Boost completed

Durable boost lifecycle event, not a human impression. Reconcile current state with the member boost endpoint.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "boost.completed",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "generation": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "remaining_ms": 0
  }
}

data

FieldDescription
memberrequired
string
Your member external_id
generationrequired
string (uuid)
remaining_msrequired
integer
≥ 0

like.received

Someone from another app liked your user

Show it as an incoming like. Liking back via POST /v1/likes creates a match.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "like.received",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "to": "string",
    "from": {
      "network_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
      "partner": {
        "id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "name": "string"
      },
      "display_name": "string",
      "age": 1,
      "gender": "male",
      "city": "string",
      "country": "string",
      "distance_km": 1,
      "languages": [
        "string"
      ],
      "bio": "string",
      "bio_original": "string",
      "bio_translated": true,
      "photos": [
        "https://example.com/photo.jpg"
      ],
      "videos": [
        {
          "url": "https://example.com/photo.jpg",
          "poster": "https://example.com/photo.jpg"
        }
      ],
      "photo_verified": true,
      "verified_by": "partner",
      "intents": [
        "string"
      ],
      "arrangement_role": "string",
      "height_cm": 1,
      "super_like": {
        "message": "string",
        "message_original": "string",
        "translated": true,
        "lang": "string",
        "flags": [
          "link"
        ]
      }
    },
    "super": true
  }
}

data

FieldDescription
torequired
string
Your user's external_id
fromrequired
object
A member of another app as seen by your user.
network_idrequired
string (uuid)
partnerrequired
object
id
string (uuid)
name
string
Name of the platform where the person registered.
display_namerequired
string
agerequired
integer
genderrequired
"male" | "female"
cityrequired
string
country
string
ISO 3166-1 alpha-2
distance_km
integer
only if this person turned on show_distance. Kilometres from the search centre (viewer's city, or lat/lon)
languagesrequired
string[]
biorequired
string
In the viewer's main language when the viewer doesn't speak the original
bio_original
string
bio_translatedrequired
boolean
Show a "translated" mark when true
photosrequired
string (uri)[]
videos
object[]
but disabled in the production executable pending frame moderation. Absent when videos capability is false.
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verifiedrequired
boolean
The person's app verified they match the photos, and the network counts it
verified_by
"partner"
Who verified the photos: partner — the person's app, with a method approved by the network
intentsrequired
string[]
arrangement_role
string
Shown only to viewers who have the arrangement intent themselves
height_cm
integer
Absent when the person didn't specify it
super_like
object
This person sent the viewer a super like (feed, incoming likes, like.received)
message
string
In the viewer's main language when they don't speak the original
message_original
string
translated
boolean
lang
string
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )
super
boolean
A super like: from.super_like has the message

like.withdrawn

A like your user hasn't answered is gone

Remove it from your user's incoming likes. it also comes when the person who liked is no longer available — deleting their account, hidden after reports, their profile rejected by moderation, or removed from the network (reason). If they come back (restored, reinstated, profile fixed), your user gets like.received again.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "like.withdrawn",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "to": "string",
    "from": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "reason": "unliked"
  }
}

data

FieldDescription
torequired
string
Your user's external_id
fromrequired
string (uuid)
network_id of the person who took the like back
reason
"unliked" | "account_deleting" | "suspended" | "rejected" | "account_deleted" | "left_network"
unliked — they took the like back themselves

match.created

A match was created

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "match.created",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "member": "string",
    "with": {
      "network_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
      "partner": {
        "id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
        "name": "string"
      },
      "display_name": "string",
      "age": 1,
      "gender": "male",
      "city": "string",
      "country": "string",
      "distance_km": 1,
      "languages": [
        "string"
      ],
      "bio": "string",
      "bio_original": "string",
      "bio_translated": true,
      "photos": [
        "https://example.com/photo.jpg"
      ],
      "videos": [
        {
          "url": "https://example.com/photo.jpg",
          "poster": "https://example.com/photo.jpg"
        }
      ],
      "photo_verified": true,
      "verified_by": "partner",
      "intents": [
        "string"
      ],
      "arrangement_role": "string",
      "height_cm": 1,
      "super_like": {
        "message": "string",
        "message_original": "string",
        "translated": true,
        "lang": "string",
        "flags": [
          "link"
        ]
      }
    }
  }
}

data

FieldDescription
match_idrequired
string (uuid)
memberrequired
string
Your user's external_id
withrequired
object
A member of another app as seen by your user.
network_idrequired
string (uuid)
partnerrequired
object
id
string (uuid)
name
string
Name of the platform where the person registered.
display_namerequired
string
agerequired
integer
genderrequired
"male" | "female"
cityrequired
string
country
string
ISO 3166-1 alpha-2
distance_km
integer
only if this person turned on show_distance. Kilometres from the search centre (viewer's city, or lat/lon)
languagesrequired
string[]
biorequired
string
In the viewer's main language when the viewer doesn't speak the original
bio_original
string
bio_translatedrequired
boolean
Show a "translated" mark when true
photosrequired
string (uri)[]
videos
object[]
but disabled in the production executable pending frame moderation. Absent when videos capability is false.
urlrequired
string (uri)
Public MP4, at most 15 seconds and 20 MB
posterrequired
string (uri)
Still frame. Shown before playback and by apps that do not play video. Moderated like a photo
photo_verifiedrequired
boolean
The person's app verified they match the photos, and the network counts it
verified_by
"partner"
Who verified the photos: partner — the person's app, with a method approved by the network
intentsrequired
string[]
arrangement_role
string
Shown only to viewers who have the arrangement intent themselves
height_cm
integer
Absent when the person didn't specify it
super_like
object
This person sent the viewer a super like (feed, incoming likes, like.received)
message
string
In the viewer's main language when they don't speak the original
message_original
string
translated
boolean
lang
string
flags
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )

message.created

A new message for your user

Show a warning when suspicious is true — flags say why.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "message.created",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "to": "string",
    "message": {
      "id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
      "from": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
      "client_message_id": "string",
      "text": "string",
      "text_original": "string",
      "translated": true,
      "lang": "string",
      "created_at": "2026-10-24T12:00:00Z",
      "suspicious": true,
      "flags": [
        "link"
      ]
    }
  }
}

data

FieldDescription
match_idrequired
string (uuid)
torequired
string
Your user's external_id
messagerequired
object
idrequired
string (uuid)
Use it as up_to in markRead
fromrequired
string (uuid)
Sender's network_id
client_message_id
string
Sender-scoped retry ID when supplied; deduplicate incoming messages by message.id
textrequired
string
In your user's language when translated
text_original
string
translatedrequired
boolean
langrequired
string
Language of text
created_atrequired
string (date-time)
suspiciousrequired
boolean
flagsrequired
"link" | "phone" | "messenger" | "money" | "mass_text"[]
Scam signals: link, phone, messenger (early in a chat), money, mass_text (the same text in several matches, only together with another signal )

message.read

The other side read your user's messages

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "message.read",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "member": "string",
    "by": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "up_to": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "read_at": "2026-10-24T12:00:00Z"
  }
}

data

FieldDescription
match_idrequired
string (uuid)
memberrequired
string
Your user's external_id (the author)
byrequired
string (uuid)
network_id of the reader
up_torequired
string (uuid)
Messages up to this one (inclusive) are read
read_atrequired
string (date-time)

match.closed

A match of your user was closed by the other side

Make the chat read-only; the history stays. With peer_deleted: true (the other person deleted their account or their network data) also replace their name and photo with "Account deleted" / "Left the network".

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "match.closed",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "member": "string",
    "reason": "unmatched",
    "peer_deleted": true
  }
}

data

FieldDescription
match_idrequired
string (uuid)
memberrequired
string
reasonrequired
"unmatched" | "blocked" | "left_network" | "account_deleted"
peer_deleted
boolean
The other person is gone from the network: their profile is erased

match.erased

A match of your user was erased by the other side

The other side deleted the pair (or blocked with erase) — remove the chat and its history on your side.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "match.erased",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "member": "string",
    "reason": "unmatched"
  }
}

data

FieldDescription
match_idrequired
string (uuid)
memberrequired
string
Your user's external_id
reasonrequired
"unmatched" | "blocked"

match.frozen

The other person is deleting their account or was hidden after reports

The chat is read-only (409 match_frozen). reason: account_deleting — show "Account deleted"; it may come back with match.unfrozen (restored within 30 days) or end with match.closed + peer_deleted. suspended — the network hid them after reports; show "Account unavailable" until match.unfrozen (their platform reinstated them) or match.closed. partner_suspended means either platform is suspended. Restore messaging only after an unfreeze or a fresh bootstrap confirming frozen: false; another independent freeze may remain.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "match.frozen",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "member": "string",
    "reason": "account_deleting"
  }
}

data

FieldDescription
match_idrequired
string (uuid)
memberrequired
string
Your user's external_id
reason
"account_deleting" | "suspended" | "partner_suspended"
partner_suspended

match.unfrozen

The other person restored their account or was reinstated after reports

The chat is back as it was — their name, photo and messaging.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "match.unfrozen",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "match_id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "member": "string"
  }
}

data

FieldDescription
match_idrequired
string (uuid)
memberrequired
string
Your user's external_id

member.reported

Your user was reported by someone from another app

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "member.reported",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "reason": "string",
    "comment": "string"
  }
}

data

FieldDescription
member
string
reason
string
comment
string

member.suspended

Your user was hidden from the network pending review

After a report with underage or commercial, or reports from 3 different people. Their matches are frozen until you review the reports: block the user in your app or bring them back with POST /v1/members/{external_id}/reinstate.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "member.suspended",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "reason": "string"
  }
}

data

FieldDescription
member
string
reason
string

member.rejected

Your user's profile did not pass the network's moderation

The profile is hidden from feeds and cannot like until you send a corrected one.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "member.rejected",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "member": "string",
    "reason": "photo_nudity",
    "photo": "https://example.com/photo.jpg",
    "video": "https://example.com/photo.jpg"
  }
}

data

FieldDescription
memberrequired
string
reasonrequired
"photo_nudity" | "offensive_text" | "contact_info" | "photo_unavailable" | "video_invalid" | "video_too_long" | "video_unavailable"
photo
string (uri)
The photo or poster that did not pass (photo_* reasons)
video
string (uri)
The clip that did not pass (video_* reasons)

photo_check.updated

A new result of a photo check

After each attempt, when the person disputes the result and when the network moderator decides — go by the latest. Passed (passed or resolved with the photos matched) — you may claim verification: {provider: "pereval", check_id}.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "photo_check.updated",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "id": "6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
    "ref": "string",
    "url": "https://example.com/photo.jpg",
    "status": "created",
    "reason": "no_match",
    "attempts": 1,
    "expires_at": "2026-10-24T12:00:00Z",
    "checked_at": "2026-10-24T12:00:00Z",
    "dispute_until": "2026-10-24T12:00:00Z",
    "disputed_at": "2026-10-24T12:00:00Z",
    "decided_at": "2026-10-24T12:00:00Z",
    "note": "string",
    "photos": [
      {
        "id": "string",
        "url": "https://example.com/photo.jpg",
        "verdict": "match"
      }
    ],
    "created_at": "2026-10-24T12:00:00Z"
  }
}

data

FieldDescription
idrequired
string (uuid)
refrequired
string
url
string (uri)
Only in the POST reply: the check page to open for the person
statusrequired
"created" | "passed" | "partial" | "failed" | "disputed" | "resolved" | "expired"
created — not taken yet; passed — it's the person on every photo with a face; partial — some photos don't look like the picture; failed — see reason; disputed — the person disputed, a network moderator decides (usually within 24 hours); resolved — the moderator decided (note explains); expired — the link expired untaken.
reason
"no_match" | "no_face" | "many_faces" | "no_gesture" | "virtual_camera"
Why it failed
attemptsrequired
integer
Pictures taken (up to 5 per session)
expires_atrequired
string (date-time)
Until when the link allows attempts
checked_at
string (date-time)
The last picture
dispute_until
string (date-time)
Failed or partial: the person can dispute until then
disputed_at
string (date-time)
decided_at
string (date-time)
The moderator's decision
note
string
The moderator's explanation for the person
photosrequired
object[]
idrequired
string
urlrequired
string (uri)
verdict
"match" | "no_match" | "unsure" | "no_face"
match — the same person; no_match — doesn't look like them; unsure — couldn't compare reliably; no_face — no face, not counted. Absent — not compared yet
created_atrequired
string (date-time)

ping

Test event from the dashboard

Sent when you click "Send a test event" in the dashboard. Reply 2xx — this checks the URL and the signature.

{
  "id": "evt_6f1c2b4e-8a3d-4c55-9b1e-2f0a7d9c1e44",
  "type": "ping",
  "created_at": "2026-10-24T12:00:00Z",
  "data": {
    "message": "string"
  }
}

data

FieldDescription
message
string

Unclear? Ask a developer

Read the reply and continue the conversation on this site using the private link you receive after sending. Incoming email is not accepted.