akwitoEU VAT proof

Watches

A watch re-runs a check on one VAT number on its own — monthly, weekly or daily — and records what changed since the previous run. Each run is a normal check: it consumes quota, is deduplicated over 24 h and leaves its own proof.

How many numbers an account may watch is set by its plan (free 0, starter 200, business 2 000, pro 10 000). Weekly monitoring is sold from business up, daily monitoring with pro only; starter watches monthly. A daily watch always asks the authority again: its next run is scheduled a full day after the previous one actually ran, past the 24 h VIES cache and the 24 h deduplication window, and each run costs one check of the quota. Watches beyond the plan's limit — after a downgrade, say — are paused, never deleted: they stop running and stop consuming quota, and they resume as they were once the plan allows them again.

The watch object #

json
{
  "watch_id": "wat_01J9X0000000000000000000",
  "vat_number": "FR40303265045",
  "country": "FR",
  "label": "Chanel",
  "frequency": "monthly",
  "candidate": { "name": "CHANEL", "street": "135 AV CHARLES DE GAULLE", "postcode": "92200", "city": "NEUILLY SUR SEINE" },
  "next_run_at": "2026-10-16T09:00:00Z",
  "last_run_at": "2026-09-16T09:00:00Z",
  "last_valid": true,
  "last_confidence": "confirmed",
  "last_check_id": "chk_01J9X0000000000000000000",
  "last_check_url": "https://api.akwito.eu/v1/checks/chk_01J9X0000000000000000000",
  "changed_at": null,
  "created_at": "2026-09-16T09:00:00Z"
}

candidate is null when the watch runs in lookup mode. The last_* fields are the state of the last run: it is what the next run compares itself to. They are all null on a fresh watch — null, never an empty string — and last_check_id and last_check_url also become null when retention purges the proof — the next run then behaves like a first run and announces nothing. changed_at is the date of the last change, not of the last run.

POST /v1/watches #

json
{
  "vat_number": "FR40303265045",
  "label": "Chanel",
  "candidate": { "name": "CHANEL", "street": "", "postcode": "92200", "city": "NEUILLY SUR SEINE" },
  "frequency": "monthly"
}

201 Created with the watch object. Only vat_number is required. frequency defaults to monthly. A candidate with a non-empty name runs every check in verify mode; without a name it is ignored, since there is nothing to confirm. The first check is scheduled immediately (next_run_at is now), so the watch has a reference state within the hour.

HTTP statuserror.codeWhen
400INVALID_FORMATvat_number could not be normalised
400BAD_REQUESTmalformed JSON, unknown field, or a frequency other than monthly/weekly/daily
403WATCH_LIMITthe plan's watch limit is reached; carries limit
403FREQUENCY_NOT_ALLOWEDweekly or daily on a plan that does not sell it
409WATCH_EXISTSthis account already watches this number

POST /v1/watches/import #

json
{ "batch_id": "bat_01J9X0000000000000000000" }

Watches every done line of a batch, monthly. Lines that are still pending or failed are ignored: they carry no usable number.

json
{ "created": 812, "skipped_existing": 14, "skipped_limit": 0, "skipped_invalid": 0 }

201 Created. skipped_existing counts the numbers already watched, skipped_limit those left out because the plan's limit was reached, and skipped_invalid those whose stored number can no longer be normalised. The four counters add up to the batch's done lines: nothing is left out in silence. An unknown batch, or one owned by another account, is 404 NOT_FOUND.

A line's identity columns follow the same rule as POST /v1/watches: a candidate is kept only when it carries a name, so a verify line that held an address and no name becomes a lookup watch.

GET /v1/watches #

Lists the account's watches, newest first. limit (1 to 100, default 20) and cursor behave as on the other lists; items have the shape of the watch object.

GET /v1/watches/{id} #

The watch object, or 404 NOT_FOUND when it does not exist or belongs to another account.

DELETE /v1/watches/{id} #

Removes the watch and its change log, 204 No Content. The checks it produced are kept: they are evidence, and they remain readable at their own proof_url.

GET /v1/watches/events #

The account's change log, newest first, limit and cursor as above.

json
{
  "items": [
    {
      "event_id": "wev_01J9X0000000000000000000",
      "watch_id": "wat_01J9X0000000000000000000",
      "kind": "became_invalid",
      "check_id": "chk_01J9X0000000000000000000",
      "check_url": "https://api.akwito.eu/v1/checks/chk_01J9X0000000000000000000",
      "before": { "valid": true, "confidence": "confirmed", "name": "CHANEL", "address": "135 AV CHARLES DE GAULLE, 92200 NEUILLY SUR SEINE" },
      "after": { "valid": false, "confidence": "invalid" },
      "created_at": "2026-09-16T09:00:00Z"
    }
  ],
  "next_cursor": ""
}

kind is one of became_invalid, became_valid, identity_changed, confidence_changed; a single run can produce several. before and after are rendered as they were logged, so an event stays readable even once the check it points to has been purged.