API reference
VayaPin API version 1.0.0.
Base URLs:
https://api.vayapin.com— Production server
Every endpoint needs Authorization: Bearer YOUR_API_TOKEN. See
Getting started for tokens, error shapes and quotas,
and Receiving webhooks for the pin.address_updated event.
/api-reference renders the same spec with a request builder. This page is the readable version. The spec itself is at /openapi.json.
POST /v1/requests
Ask VayaPin for a recipient's address on behalf of a merchant (VayaConnect store plugins).
The account must have an enabled webhook endpoint provisioned before it can create requests; the resolved address is always delivered to that endpoint, never in this response.
Two outcomes are possible, both returned as 201 Created and distinguished by data.status:
resolved(express path) - the recipient's email or phone already has a pin. The pin is linked to the request and the address webhook is dispatched immediately.pending- no pin matched, so a claimable pin is created and the recipient is emailed a claim link. The webhook fires later, once the recipient completes or updates their pin.
Send idempotency_key (for example the merchant's order id) to make retries safe: a repeat
call with a key already used by the account returns the original request with 200 OK
instead of creating a second one. The key is read from the request body only; an
Idempotency-Key HTTP header is ignored.
Phone-only requests are not supported yet - email is required.
The resolved address is delivered as a pin.address_updated webhook to the account's webhook
endpoint (set up in the VayaPin dashboard under Developer > VayaCore > Webhooks) - see Webhooks.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
data | object | Yes | — |
data.name | string | Yes | Full name of the recipient (first + last), used to derive the pin name Example: John Example. |
data.first_name | ['string', 'null'] | No | First name (overrides the split from name) Example: John. |
data.last_name | ['string', 'null'] | No | Last name (overrides the split from name) Example: Example. |
data.email | string (email) | Yes | Recipient email. Required - it is both the pin match key and the claim-link channel Example: john@example.com. |
data.phone | ['string', 'null'] | No | Recipient phone in E.164 format. Used as an additional pin match key Example: +4512345678. |
data.locale | ['string', 'null'] | No | Locale used for the recipient-facing claim email Example: da-DK. |
data.platform | ['string', 'null'] | No | Free-text name of the platform the request comes from, for your own reporting (for example woocommerce); there is no fixed list Example: woocommerce. |
data.idempotency_key | ['string', 'null'] | No | Merchant-side unique key (e.g. order id). Unique per account; replays return the original request Example: order-42. |
data.country | string | Yes | ISO 3166-1 alpha-2 country code of the shipping address. The address fields below are optional for every country here, unlike POST /v1/pins Example: DE. |
data.address_line_1 | ['string', 'null'] | No | Example: Example Street 1. |
data.address_line_2 | ['string', 'null'] | No | Example: Suite 2. |
data.zip_code | ['string', 'null'] | No | Example: 10115. |
data.city | ['string', 'null'] | No | Example: Berlin. |
data.state | ['string', 'null'] | No | Example: BE. |
Responses
| Status | Description |
|---|---|
200 | request replayed (idempotency key already used) |
201 | request created |
400 | body is not wrapped in data |
401 | unauthorized |
422 | request rejected |
429 | quota exceeded |
POST /v1/pins
Create a new pin by provisioning an account, subscription request, and data set update.
The full VayaPin identifier of the new pin is data.account.default_country + : + data.pin_name
(for example DE:EXAMPLE).
A 207 Multi-Status response means the pin was created but some sections of its data set could
not be saved; data.pin_data_set_update.sections reports the result of every section.
Required address fields depend on country. Most countries need address_line_1, zip_code
and city; some need fewer or also state, and some check the zip_code format. A missing or
malformed field is reported in data.errors of the 422 response, for example
"city": ["is required for UA addresses"].
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
data | object | Yes | — |
data.country | string | Yes | ISO 3166-1 alpha-2 country code Example: DE. |
data.name | string | Yes | Full name (first + last), used to derive pin name Example: John Example. |
data.first_name | ['string', 'null'] | No | First name (overrides split from name) Example: John. |
data.last_name | ['string', 'null'] | No | Last name (overrides split from name) Example: Example. |
data.email | string (email) | Yes | Example: john@example.com. |
data.phone_number | ['string', 'null'] | No | Example: +1234567890. |
data.address_line_1 | ['string', 'null'] | No | Street and number Example: Example Street 1. |
data.address_line_2 | ['string', 'null'] | No | Example: Suite 2. |
data.zip_code | ['string', 'null'] | No | Example: 10115. |
data.city | ['string', 'null'] | No | Example: Berlin. |
data.state | ['string', 'null'] | No | Example: BE. |
Responses
| Status | Description |
|---|---|
200 | pin created |
207 | pin created, data set partially saved |
401 | unauthorized |
422 | validation error - invalid input |
429 | too many requests |
GET /v1/pins/{id}
Retrieve pin data by its VayaPin identifier. Returns data based on account abilities and excludes password-protected fields.
Parameters
| Name | In | Required | Description |
|---|---|---|---|
id | path | Yes | VayaPin identifier (e.g., DK:EXAMPLE) |
Responses
| Status | Description |
|---|---|
200 | successful |
401 | unauthorized |
403 | forbidden - expired subscription |
404 | not found |
429 | too many requests |
GET /v1/usage/alerts
Get API quota usage status
Returns the current API quota usage status for the authenticated account. This endpoint provides real-time information about monthly and daily quota usage, including percentages that can be used to display alerts in your UI (e.g., when usage >= 80% or >= 100%).
This endpoint does not consume quota - checking your usage is free.
Responses
| Status | Description |
|---|---|
200 | successful - no subscription |
401 | unauthorized |
Webhooks
Events VayaPin sends to your registered endpoint. See Receiving webhooks for verification and retry handling.
pin.address_updated
Sent to the webhook endpoint URL registered for the account in the VayaPin dashboard (Developer > VayaCore > Webhooks). Sent when a POST /v1/requests request resolves (immediately on the express path, otherwise once the recipient completes their pin) and again whenever the linked pin's address changes later.
The body is signed with HMAC-SHA256 over the exact raw request body, using the endpoint's
signing secret shown in the dashboard when the webhook is created. The signature is sent as
X-VayaPin-Signature: sha256=<hex>; compare it in constant time before trusting the payload.
Respond with any 2xx to acknowledge. 5xx responses and timeouts are retried (up to
5 attempts) with the same event_id, so deduplicate on it; 4xx responses are not retried.
Headers
| Header | Description |
|---|---|
X-VayaPin-Signature | HMAC-SHA256 of the raw body keyed with the endpoint signing secret, formatted as sha256=<hex> |
X-VayaPin-Event-Id | Same value as event_id in the body; stable across retries |
Payload (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string (uuid) | Yes | Unique event identifier; reused on retries of the same event |
event_type | string | Yes | — |
occurred_at | string (date-time) | Yes | — |
request_id | string (uuid) | Yes | data.request_id returned by POST /v1/requests |
vayapin_id | string | Yes | Example: DK:LARSEN12. |
scripts | object | Yes | Formatted one-line address per language. latin (English) is always present; native is added when the country's default language is not English and a native address could be resolved (e.g. Danish for DK). |
scripts.latin | object | Yes | — |
scripts.latin.label | ['string', 'null'] | Yes | Address formatted in English; null when the address could not be geocoded |
scripts.latin.display | ['string', 'null'] | Yes | Address formatted in the key's language; null when the address could not be geocoded |
scripts.native | object | No | — |
scripts.native.label | ['string', 'null'] | Yes | Address formatted in English; null when the address could not be geocoded |
scripts.native.display | ['string', 'null'] | Yes | Address formatted in the key's language; null when the address could not be geocoded |
gps | ['object', 'null'] | No | — |
qrcode | object | Yes | — |
qrcode.img_url | string (uri) | No | — |
contact | object | Yes | Recipient contact details stored on the pin, for reconciliation |
contact.email | ['string', 'null'] | No | — |
contact.phone | ['string', 'null'] | No | — |
contact.name | ['string', 'null'] | No | — |
{
"event_id": "0b6c2f4e-8d1a-4c3b-9e7f-1a2b3c4d5e6f",
"event_type": "pin.address_updated",
"occurred_at": "2026-10-08T12:34:56Z",
"request_id": "c6f8b1d9-7b0e-4d1f-8b2a-7f2b9e88d2b3",
"vayapin_id": "DK:LARSEN12",
"scripts": {
"latin": {
"label": "Vesterbrogade 1, 1620 K\u00f8benhavn V, Danmark",
"display": "Vesterbrogade 1, 1620 K\u00f8benhavn V, Danmark"
}
},
"gps": {
"lat": 55.6761,
"lng": 12.5683
},
"qrcode": {
"img_url": "https://api.vayapin.com/r/qrc/p/DK:LARSEN12/webapp/DK_LARSEN12.png"
},
"contact": {
"email": "lars@example.com",
"phone": "+4512345678",
"name": "Lars Larsen"
}
}