Skip to content
v1
REST
Connected to SRC

Docs for developers

Register sales, prepayments and refunds with the Armenian tax authority from any stack.

Base URLhttps://vcr.am/api/v1

Quick start

Register a sale with a single POST request.

bash
curl https://vcr.am/api/v1/sales \
  -H "X-API-Key: $VCR_AM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cashier": { "id": 1 },
    "items": [{
      "offer": { "externalId": "SKU-001" },
      "department": { "id": 1 },
      "quantity": "1",
      "price": "20000",
      "unit": "pc"
    }],
    "amount": { "nonCash": "20000" },
    "buyer": {
      "type": "individual",
      "receipt": { "email": "buyer@example.com", "language": "en" }
    }
  }'

Start without tax credentials

A sandbox register is issued its registration number the moment you create it — no certificate exchange with the tax authority, nothing to wait for. Same base URL, same endpoints, same key mechanics; only the receipts differ, marked TEST and never filed.

  1. 1In the dashboard open Virtual Cash Registers, click Create VCR and pick "Sandbox cash register". Create as many as you need.
  2. 2Issue an API key on that register. A key belongs to one register, so a key taken from a production one keeps answering 403.
  3. 3Call the same endpoints at the same base URL. When you are ready for real fiscalization, create a production register and swap the key.

Built for production integrations

REST over JSON
Stateless JSON endpoints you can call from any language, framework or runtime.
Typed Node.js & PHP SDKs
End-to-end typed clients on npm and Packagist, ready to drop into your Node.js or PHP server code.
API-key authentication
Scoped, rotatable API keys managed from your dashboard.
Complete cash-register API
Sales, prepayments, refunds and receipts through a single consistent surface.

What your system must provide

Before integrating, make sure each sale carries the data SRC requires.

Classifier code
Commodity code for goods or activity code for services.
Measurement unit
kg, pcs, m², hour, service, work, etc.
Department ID
Where the sale lands in your register — and the tax regime printed on the receipt. Optional: omit it and the item takes its offer's own department.
Cashier ID
The person registering the sale.

Authentication

Every request must include your API key in the X-API-Key header. The one exception is GET /exchange-rate, which is public and needs no key.

X-API-Key: your_api_key

Generate and rotate API keys in your cash register settings.

Idempotency

A fiscal receipt cannot be corrected — only refunded and reissued. That makes a duplicate the most expensive mistake this API can make, and a payment webhook firing twice is normal rather than exceptional. Send an Idempotency-Key header on every POST: a repeat of the same request returns the original receipt instead of filing a second one.

Idempotency-Key: 3f6b2c1e-9a84-4d77-9f2a-1c5e0b7d8a63
What to use as the key

A UUID generated once per intended fiscal operation and stored next to the order before the first call, then reused for every retry. Not the order number by itself — one order can legitimately produce several fiscal documents. Not a fresh UUID per attempt, which defeats the purpose.

Telling a replay from a new receipt

A replayed response is byte-identical to the original and carries the header Idempotent-Replay: true. Check it before treating a response as a newly filed document.

When the webhook can fire twice

A stateless handler has nothing stored yet, so derive the key instead of generating it. Both deliveries then compute the same key and the second one replays. Do not derive it from the payment provider's event id: a different event about the same order would produce a different key and fiscalize again.

import { v5 as uuidv5 } from "uuid";

const NAMESPACE = "6f1d1a58-0d0c-4a1e-9a1a-2f9a7b3c4d5e";
const key = uuidv5(`${order.id}:sale`, NAMESPACE);

await fetch("https://vcr.am/api/v1/sales", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.VCR_API_KEY,
    "Idempotency-Key": key,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});

A key stays replayable for 30 days — long enough that a monthly reconciliation re-run replays instead of filing again. Reusing a key with a different body answers 422; reusing one while the first request is still running answers 409, which means wait and retry the same key, not mint a new one. A body the schema rejected spends no key at all: the request never reached the operation, so correct the field and retry with the same key.

Errors

Errors use standard HTTP status codes and a JSON body with the details.

StatusNameWhen it happens
400
Bad requestMalformed payload or schema validation failed.
401
UnauthorizedMissing or invalid API key.
403
ForbiddenThe register cannot file documents yet: no registration number, or not activated at the tax authority.
404
Not foundThe requested resource doesn't exist.
409
ConflictThe tax authority refused the document on its merits, an inline offer clashes with an existing one, or a request with the same Idempotency-Key is still running. A refusal carries `pending`: the document IS saved, so fix the cause rather than resending.
422
Key reusedThe same Idempotency-Key arrived with a different body. A new document needs a new key.
500
Internal errorUnexpected failure on our side. Retry with exponential backoff.
502
Tax authority unreachableThe document IS saved and the response carries `pending`. Read `pending.mayResubmit` first: false means a resend would create a second receipt, so poll `pending.statusUrl` instead.

Further reading

Prepayments and advances
The two-receipt model the law requires when money arrives before the goods do, and how to settle the balance later.

Continue building

API explorer
Interactive OpenAPI explorer.
Node.js / TypeScript
Next.js, NestJS, Express, Bun, Deno, plain Node.js
WooCommerce
WordPress + WooCommerce store
WooCommerce plugin
Install from GitHub

Coming to the WordPress.org Plugin Directory