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 URL | https://api.pereval.dating — for test and live; the key selects the environment |
| Auth | Authorization: Bearer <api_key> — server-side only |
| Format | JSON, UTF-8, times RFC 3339 UTC |
| Errors | {"error": "<code>", "message": "…"} — all codes |
| Rate limit | 600 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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 |
message says which fieldAdd 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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.
|
location_change_limited — the city can change once a daymessage says which fieldErase 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
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
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
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
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
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
| Field | Description |
|---|---|
| external_idsrequired string[] | 1–1000 items |
Responses
| Field | Description |
|---|---|
| updated integer |
message says which fieldFeed
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
| Field | Description |
|---|---|
| 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. |
world_consent_required — scope=world for a member without consent.world; member_hidden — the member is hidden (consent.network: false)message says which fieldLikes & 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.receivedagain. - While your user's profile is in moderation, their likes are stored but the recipient gets
like.receivedonly 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| matchedrequired boolean | |
| match_id string (uuid) | Present when matched |
member_hidden — a hidden member can only like back someone who liked them, without a super likemessage says which fieldsuper_like_limit — more than 5 super likes in 24 hTake 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
matched — the like already became a matchBring 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
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
| Field | Description |
|---|---|
| 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
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
| Field | Description |
|---|---|
| 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) |
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
| Field | Description |
|---|---|
| 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
match.closed) or erased (match.erased)message says which fieldMessages
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
| Field | Description |
|---|---|
| asrequired string | external_id of your user |
Responses
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
| Field | Description |
|---|---|
| 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 |
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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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 |
message says which fieldfirst_messages_limit — a new member started too many conversations today, or rate_limitedYour 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
| Field | Description |
|---|---|
| asrequired string | external_id of the reader |
| up_torequired string (uuid) | Id of the last message read |
Responses
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
| Field | Description |
|---|---|
| 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
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
| Field | Description |
|---|---|
| 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
message says which fieldLanguages
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
| Field | Description |
|---|---|
| translatedrequired string[] | ISO 639-1 codes |
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
| Field | Description |
|---|---|
| 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
url for the person| Field | Description |
|---|---|
| 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) |
message says which fieldphoto_check_limit — more than 10 checks for this person (ref) a day; or rate_limitedphoto_check_off — the comparison service is temporarily unavailable; retry laterPhoto 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
url)| Field | Description |
|---|---|
| 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) |
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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| jsonrpcrequired "2.0" | |
| id object | number, string or null (null = notification, no response) |
| methodrequired string | |
| params object |
Responses
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
| Field | Description |
|---|---|
| generationrequired string (uuid) | |
| staterequired "active" | "paused" | "completed" | |
| started_atrequired string (date-time) | |
| remaining_msrequired integer | ≥ 0 |
| api_placementsrequired integer | ≥ 0 |
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
| Field | Description |
|---|---|
| formatrequired "swipe" | "search" | "none" | |
| enabledrequired boolean |
Responses
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
| Field | Description |
|---|---|
| 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" |
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
| Field | Description |
|---|---|
| currencyrequired "TEST" | "RUB" | |
| test_onlyrequired boolean | |
| balancerequired integer (int64) | ≥ 0 |
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
| Field | Description |
|---|---|
| memberrequired string | 1–256 chars |
| skurequired string | 1–80 chars |
| quantityrequired integer | 1–1000000 |
Responses
| Field | Description |
|---|---|
| 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" |
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
| Field | Description |
|---|---|
| quote_idrequired string (uuid) | |
| external_order_refrequired string | 1–128 chars |
Responses
| Field | Description |
|---|---|
| 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) |
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
| Field | Description |
|---|---|
| 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) |
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
| Field | Description |
|---|---|
| enabledrequired boolean |
Responses
| Field | Description |
|---|---|
| enabledrequired boolean |
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
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
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
| Field | Description |
|---|---|
| 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 |
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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| 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
| Field | Description |
|---|---|
| message string |