Skip to main content
Glama
Amiqus MCP server

Amiqus MCP server

by dragosh29

Amiqus MCP server

An MCP server that lets Claude, ChatGPT and other MCP clients read an Amiqus ID account (client onboarding with identity, AML, right-to-work and criminal-record checks): clients, records and their steps, check results, templates, case status counts and webhooks, and (when enabled) create records. It is built from Amiqus's public developer documentation and its published OpenAPI 3.1 spec, and nothing else.

Once it's connected, someone on the team can ask things like:

  • "Where is Martin McFly's onboarding? Which steps are still open?"

  • "Which records are waiting on check results, and who created them?"

  • "Did the photo ID check on record 983500 pass? What needed consideration?"

  • "How many cases need action from us this month?"

  • "Which record templates are enabled, and what steps does 'Identity verification' send?"

  • With writes enabled: "Send Jennifer Parker the Identity verification template."

Tools

Tool

What it does

API calls

list_clients

Clients with name, decision status, reference, retention date and timestamps. Filters: fuzzy search, status, visibility, assignee, exact reference, deletion_date bucket, sort_by and order_by; pages by page/limit.

GET /clients

get_client

One client.

GET /clients/{id}

list_client_records

Every record sent to one client, with its steps.

GET /clients/{id}/records

list_records

Records across the team. Filters: status, visibility, created_by, assignee (a user ID, or false for unassigned), client_visibility, sort_by, order_by.

GET /records

get_record

One record with its steps. By default the steps are fetched with expand=check,review, so each check step shows the check's status and the latest team-member review.

GET /records/{id}, GET /records/{id}/steps?expand=check,review

get_check

One check with its response expanded: overall result, and per report the status, result and verification breakdown.

GET /checks/{id}?expand=response

list_templates

Record templates (preset steps, notification, message, reminders, assignees), email templates or document templates; enabled filter.

GET /templates/records, /templates/emails, /templates/documents

case_status_summary

Count of cases per status, with assigned_to (ID or false), start_date, end_date and visibility.

GET /aggregates/case-status

list_webhooks

Webhook subscriptions: URL (origin and path), events, enabled flag. Never the signing secret.

GET /webhooks

create_record

Creates a record for an existing client from a record template (template_id) or from an explicit step list with notification ("email" or false), message, reminder and is_declaration_required. Refuses locally a request that mixes the two forms or lacks notification in the step form. Only registered when writes are enabled.

POST /records

Not covered on purpose: the user and team endpoints, client addresses, organisations, assignees, forms and form templates, files and downloads (/records/{id}/download, /clients/{id}/files/{fileId}/download, /clients/{id}/forms/{reference}/download), documents and attachments, credits, cases and case items, step reviews (writing), SDK tokens, webhook creation, and every update, archive, expire and delete endpoint. This server never downloads a file.

Related MCP server: Timesheet Portal MCP server

Setup

Requires Node 18 or later.

npm install
npm run build

You need a personal access token for your Amiqus team. Amiqus's authentication guide says a token carries the permissions of the user who created it, is limited to the team active when it was created, expires after one year and can be revoked by the user; the API accepts it as a Bearer token. Amiqus also documents an OAuth2 authorization-code flow (/oauth/authorize, /oauth/token); this server does not implement it.

Claude Desktop: add this to claude_desktop_config.json:

{
  "mcpServers": {
    "amiqus": {
      "command": "node",
      "args": ["/absolute/path/to/amiqus-mcp/dist/index.js"],
      "env": { "AMIQUS_ACCESS_TOKEN": "your-token" }
    }
  }
}

Claude Code:

claude mcp add amiqus -e AMIQUS_ACCESS_TOKEN=your-token -- node /absolute/path/to/amiqus-mcp/dist/index.js

Variable

Required

Meaning

AMIQUS_ACCESS_TOKEN

yes

A personal access token, sent as Authorization: Bearer …. A pasted Bearer prefix is stripped.

AMIQUS_ALLOW_WRITES

no

true to register create_record. Off by default.

AMIQUS_BASE_URL

no

Defaults to https://id.amiqus.co/api/v2 (the spec's server URL). Used by the tests. Must not contain credentials.

Safety defaults

  • Read-only unless AMIQUS_ALLOW_WRITES=true. Read tools carry the MCP readOnlyHint annotation; create_record is marked as a non-destructive, non-idempotent write. There are no cancel, archive, expire or delete tools.

  • This is identity data, so the default output is names and states, not personal details. Client names, references, statuses, dates and IDs are returned by default. Only returned when a tool is called with include_contact_details=true: a client's email address, landline, mobile, date of birth and National Insurance number; a record's contact email and its perform_url (the spec calls it "the unique URL to complete the record steps in a browser", so whoever has it can submit identity documents as the client; the default output only says whether one exists); and, inside a check response, the data read from the identity document (document_data: names, date of birth, gender, document numbers, issue and expiry dates, MRZ lines, nationality, place of birth, and the nfc chip data when the chip was read), the eVisa report's name and reference, and any bare date in a check response (the eVisa's starts_at and expires_at, say; a date without a time of day is the shape of a date of birth, so it is redacted by default, while timestamps are kept). The verification breakdown (which checks were clear or consider, with scores) is returned by default: its entries named after fields (first_name, date_of_birth, document_numbers, mrz) are verdicts about those fields, not the values, and only the verdict parts of such an entry come back (result, status, type, nested verdicts, the reason's type, scores); anything else stored next to the verdict is withheld.

  • Never returned, with or without include_contact_details: identity-document images, selfies and motion captures, eVisa PDFs and any other attachment inside a check response (replaced by a placeholder and a count); the attachments of document steps (only a count); a form's answers (only the field count); a webhook's signing secret; and the query string of a webhook delivery URL (which may carry a token; the output flags that it was removed). The server never calls a download endpoint.

  • In free text (references, step preferences such as a document request's title and instructions, review messages, template names, descriptions, messages and content, eVisa conditions, error messages from Amiqus) these are replaced by default: email addresses ([email redacted]), phone-number-like sequences ([phone redacted]), dates without a time of day such as 1968-06-12, 12/06/1968, 12.06.1968, 12 June 1968 or June 12th, 1968 ([date redacted]), UK postcodes ([postcode redacted]), National Insurance numbers ([NI number redacted]) and NHS numbers, ten digits in 3-3-4 groups whose last digit passes the modulus-11 check ([NHS number redacted]). All of these are heuristics. The phone match covers international numbers written with + or 00, UK numbers with a bracketed area code, and UK-style 0… numbers of 9 to 11 digits with spaces, dots or hyphens; other digit strings starting with 0 may be caught too, and one ten-digit number in eleven passes the NHS check. Not caught: a street address written as words (12 High Street; its postcode is), a year-month (2026-08) and a date glued to letters. Left alone on purpose: numeric IDs, UUIDs, timestamps (2026-08-22T09:00:00Z), version numbers and hyphenated references such as MCFLY-1955 or REF-2026-08-22. Template text has no include_contact_details switch and is always redacted. Values nested more than 20 levels deep are replaced by a placeholder.

  • The check-response scrubbing is generic because the spec marks check responses as beta ("available data may be incomplete or differ from the specification") and documents only the Photo ID response in full. It works by key name: a key matching this list is withheld unless its value is a {result: …} verdict: name/names, first_name, last_name, middle_name, full_name, complete_name, surname, forename(s), given_name(s), holder, date_of_birth, dob, birth… (birth_date, birthday), gender, sex, nationality, place_of_birth, document_data, document_number(s), mrz, address, street, city, town, county, postcode/post_code/postal_code, zip, line1/line_1, email, phone, mobile, landline, telephone, national_insurance_number, ni, ni_number, nhs… (nhs_number, nhs_no), pin, pin_number, share_code, reference, passport, passport_number, licence_number/license_number, issuing_date, issue_date, date_of_expiry, expiry, expiry_date, personal_details, applicant, account_number, sort_code, iban, card_number, personal_number, id_number, certificate_number, identifier, nfc, each matched as a whole word within an underscore-separated key, after splitting camelCase and lower-casing (dateOfBirth, DocumentNumber and address_line_1 match; document_type does not). Anything else in a check response is returned, with the free-text redaction above applied to every string, so a personal value under a key that is not on the list would come back with its dates, postcodes and contact details redacted but its words (a name, a street) intact. Any key that looks like a file (media, attachment(s), image(s), pdf, selfie, video, photo) is always withheld.

  • IDs are checked before any call is made: every ID this server sends is a positive whole number (the spec types them as integers). The expand values sent are the documented ones (check,review on steps, response on a check), comma-separated as the expandable-properties guide shows. Filter values are the spec's enums; assignee/assigned_to take a user ID or false as documented. A date or date-time given to case_status_summary is sent as YYYY-MM-DDTHH:MM:SSZ, the form in the data-formats guide: a date alone becomes T00:00:00Z (start) or T23:59:59Z (end), missing seconds become :00, a time without a zone is taken as UTC, an offset such as +01:00 is converted to UTC and fractions of a second are dropped; an impossible calendar date (2026-02-30, 2026-13-01) or time of day is refused before any call.

  • create_record sends exactly the two documented request shapes (the spec's "Template" and "Manual" variants) and refuses a mix of them locally. Steps are passed through as given; their types and preferences are the API's, and the API's 422 field errors (steps.0.type: The selected type is invalid.) are passed on.

  • Rate limits, as documented in Amiqus's rate-limits guide: every response carries X-RateLimit-Limit and X-RateLimit-Remaining (the guide's example limit is 200, and "rate limits may differ depending on the endpoint"); once exhausted, requests get a 429 with Retry-After (seconds until the limit resets) and X-RateLimit-Reset; one limit is shared by every token of a user on a team. The window length is not documented. Requests are spaced 250 ms apart (the tests measure at least 240 ms between consecutive requests on an open connection). A 429 is retried at most twice for any method, including POST /records, on the assumption that a rate-limited request was not processed (see Status). The retry waits for Retry-After (whole or fractional seconds, or an HTTP-date; 2 s then 4 s when the header is absent or unreadable). Each wait is capped at 10 seconds, so one request waits at most 20 seconds in all; a tool call that makes several requests (pages, lookups) can still run past the MCP client's default 60-second request timeout. Past the cap: if Amiqus asks for a longer wait the call gives up at once and the message says how long to wait. The final 429 message quotes X-RateLimit-Limit and Retry-After.

  • 502, 503 and 504 are retried the same way for GET only; when all three attempts fail the error says the service may be unavailable or in maintenance (the status-codes guide describes 503 as maintenance) and to try again in a few minutes, without the gateway's HTML. A POST /records is never retried after a gateway error, because the record may already have been created and the client emailed; the error tells the assistant to check with list_records or list_client_records first. Amiqus documents no idempotency key, so there is nothing to send that would make a repeat safe.

  • A 200 whose body is not JSON (a proxy or a login page in the way) is reported as an error naming AMIQUS_BASE_URL, never as an empty list. A rejected token produces a message that says which variable to fix and never echoes the token; a 403 says the token's user may lack permission or the feature (cases, for example) may not be enabled for the team; a 404 names the path; a 422 lists Amiqus's field errors.

Tests

npm test

The test suite:

  1. Validates every fixture record against the schemas in Amiqus's published OpenAPI 3.1 spec, with Ajv 2020-12: clients (Client plus the /clients status enum), records (Record), every step (RecordSteps), checks (Check, including the documented Photo ID CheckResponse with document, facial-similarity and eVisa reports), step reviews (StepReview), record, email and document templates, webhooks and the case status aggregate. Negative controls confirm the schemas reject a string ID, an undeclared key, an unknown status and a malformed links object. The spec is downloaded from developers.amiqus.co/aqid/openapi.json to spec.json on the first run.

  2. Starts a local mock of the API under /api/v2 that serves those fixtures with the documented page/limit pagination (PaginatedList with links.next/previous carrying the current query parameters, links: null for a single page, 422 for a limit above 100, and on request pages capped below the requested limit or responses without links), Bearer-token 401s in the Error shape, the spec's default 404 texts (Client not found, Record not found, Check not found), 422 field-error maps (including the guide's "expand parameter must be one of" case), the X-RateLimit-* headers, expand=check,review on steps and expand=response on checks (with a collapsed true/null response otherwise, and no nested expansion, as the guide says), and POST /records in both documented shapes (the spec's own request examples are posted and answered with a Record). The mock's list, detail, created and error responses are validated against the spec's response schemas. The first GET /templates/records is answered with a 429.

  3. Starts the built server and drives it over stdio with the official MCP client: 33 checks covering every tool, tool annotations, paging by page/limit across two pages of 100 and stopping when links.next is null, whole-page max_results with next_page (and the short last page fetched when total says it fits), paging through short pages when the API caps the page size (with the stop rule using the applied page size and the throttle's 250 ms spacing measured between requests on an open connection), paging by current_page/total_pages when a response has no links, every documented filter of list_clients, list_records, list_templates, list_webhooks and case_status_summary passed through with the documented parameter names and values (including assignee=false, created_by rather than the deprecated creator, and seven date/date-time input forms each sent as YYYY-MM-DDTHH:MM:SSZ while nine malformed or impossible ones are refused with the intended message and no request), the default redaction and its opt-in for client contact details, a record's email and perform URL, step preferences, review messages and check responses (verdicts kept, document_data, eVisa name/reference and bare dates withheld, images and files never returned even on request), the 'not yet available' and pending check responses, the 429 retry waiting for Retry-After in the seconds, fractional-seconds and HTTP-date forms, giving up after three attempts on a persistent 429 and at once on a Retry-After above the cap, a 429 on POST /records retried once, a 502 and a 504 retried for GET and a 502 never for POST /records, a GET failing three times with 503 reported with advice and without the gateway HTML, a 403 reported with the permissions hint and the email, phone, postcode, date, NI and NHS number in Amiqus's own message redacted, a 200 with a non-JSON body reported as an error, the POST /records bodies validated against the spec's request schema and its Template and Manual branches, the local refusal of mixed or incomplete create requests and the pass-through of 422 field errors, ID validation before any call, the 401 and 404 messages, the write gate with the variable unset and set to false, a base URL with credentials refused at start-up, that every request used the Bearer token, asked for JSON and matched a documented method and path, and, directly on the formatter, the reduction of an expanded document step to counts and a form to its field count, the 20-level nesting placeholder, the withholding of values stored next to a verdict under a personal-data key (with the documented Photo ID breakdown unchanged), the key spellings and camelCase forms on the list above, NFC chip data withheld by default and returned on request, and the free-text patterns with their intended non-matches (shapes the server never requests, so the mock never serves them).

The suite makes 106 requests to the mock and takes about 35 seconds; it never contacts Amiqus except to download the spec.

Status

This is a working prototype. It has not yet been run against the live API, because it was built without an Amiqus account (there is no self-serve trial; the getting-started guide says to contact Amiqus for a sandbox). Everything below is taken from the published documentation and should be confirmed on a real account, sandbox first:

  • Authentication end to end with a personal access token, the body of a real 401 (the spec gives only {error}), and whether a token limited to one team sees exactly that team's data.

  • Pagination: that links.next is null on the last page and links null on a single page as the guide says (the server also treats current_page below total_pages as "more pages", so a response without links is still paged, and it sizes the next page from the response's limit and total), what limit and page values outside 1..100 and 1.. answer (the mock answers 422), whether any endpoint used here has a page limit below 100 (the guide says some may), and that total, total_pages, current_page and limit are always filled.

  • The default sort order of GET /clients ("ID order" per the parameter description) and GET /records ("created_at order"), and the exact semantics of the search (fuzzy) and reference (exact) filters.

  • assignee=false on GET /records and assigned_to=false on the aggregate: the spec types them as integer-or-false; the server sends the literal false as a query string.

  • The expand parameter: that expand=check,review on GET /records/{id}/steps and expand=response on GET /checks/{id} are accepted with a comma-separated list, what the expanded review looks like on a step that has never been reviewed (the spec says null) and on one that cannot be (false), and whether an embedded check's response is collapsed to true/null.

  • The shape of check responses on a live account. The spec marks them as beta and documents only the Photo ID response (check_response.photo_id) in detail, and its examples label that check type: "identity" while the Check.type enum also has photo_id; other types come back as check_response.other. Which fields carry personal data in the responses of watchlist, criminal-record, credit and other checks is unknown, which is why the scrubbing is by key name and generic. Confirm on real responses that nothing personal slips through by default.

  • Which fields the live API fills: national_insurance_number and is_declaration_required are "available where feature enabled on team only"; perform_url is false when nothing can be submitted; deletion_date may be null.

  • GET /aggregates/case-status on a team without cases (the spec says cases "may not be enabled for all teams"; the server reports a 403 with that hint) and the exact meaning of start_date/end_date ("statuses updated on or after/before").

  • POST /records: that a record created from a template with assignees: false really gets no assignees, what the created record's steps look like for document and form steps (the mock resolves a {template} preference to a title and instructions, as the spec's 201 example does), and whether a 429 on POST /records can ever arrive after the record was created (the server retries a 429 once on the assumption that it was not processed).

  • The wording of Amiqus's error messages and whether any of them echo request data; the texts here are placeholders (the 404 texts are the spec's defaults), and the server redacts contact details from them regardless.

  • The date-time format on the wire: the data-formats guide says RFC 3339 with Z, which is what the fixtures use and what ajv-formats requires.

  • How many requests per second the API tolerates within its limit; the window is not documented, so the 250 ms spacing is a guess on the polite side.

Going to production

This version runs locally over stdio, with the account holder's own personal access token. For customers to connect from claude.ai or ChatGPT without handling tokens, the next step is a remote server (Streamable HTTP) behind Amiqus's own OAuth2 authorization-code flow, hosted by Amiqus, and then a listing in the Claude and ChatGPT connector directories.

Licence

MIT. Built by Alexandru Dragoș (alexandru.dragos96@gmail.com) with an AI agent (Claude) working under his direction.

Available Tools

9 tools
case_status_summaryCase status summaryA
Read-onlyIdempotent

How many cases are in each status (awaiting_response, action_required, reviewed_pending_decision, approved, rejected, on_hold; pending is deprecated), optionally for one assignee or unassigned cases, for statuses updated in a date range, and active or archived cases. Uses GET /aggregates/case-status. The spec notes cases may not be enabled for all teams; a 403 then says so.

Inputs: end_date, start_date, visibility, assigned_to.

get_checkGet check resultA
Read-onlyIdempotent

One check by ID (from a record's check steps) with its type, status and, when Amiqus has one, the response: overall result and each report's status, result and verification breakdown (which verifications were clear or need consideration). Uses GET /checks/{id}?expand=response. The spec marks check responses as beta and documents the Photo ID response in detail; other types may come back as 'not yet available'. Identity-document data read from the document (names, date of birth, document numbers, MRZ) and the eVisa report's name and reference only with include_contact_details; the document images, selfie and PDF are never returned.

Inputs: check_id (required), include_contact_details.

get_clientGet clientA
Read-onlyIdempotent

One client by ID: name, decision status, reference, whether archived, retention (deletion) date and timestamps. Uses GET /clients/{id}. Contact details, date of birth and National Insurance number only with include_contact_details.

Inputs: client_id (required), include_contact_details.

get_recordGet recordA
Read-onlyIdempotent

One record by ID with its steps. By default the steps are fetched with their check and latest review expanded (GET /records/{id} then GET /records/{id}/steps?expand=check,review), so each check step shows the check's status (pending, submitted, accepted, rejected, refer, failed, paused) and whether a team member has reviewed it. Use get_check for a result breakdown. The record's contact email and perform URL only with include_contact_details.

Inputs: record_id (required), include_step_details, include_contact_details.

list_client_recordsRecords for a clientA
Read-onlyIdempotent

Every record (onboarding request) sent to one client, with status, the steps it contains (type, completion, cost, check/document/form IDs) and dates. Uses GET /clients/{id}/records. The record's contact email and perform URL only with include_contact_details.

Inputs: client_id (required), page, max_results, include_contact_details.

list_clientsList clientsA
Read-onlyIdempotent

Clients (the people being onboarded) with name, decision status (pending/approved/rejected, or none yet), reference, retention date and timestamps. Filter by a fuzzy search on names, reference and organisation name; by status; active or archived; assignee; exact reference; deletion-date bucket; and sort by name or date. Uses GET /clients. Emails, phone numbers, dates of birth and National Insurance numbers only with include_contact_details.

Inputs: page, search, status, sort_by, +7 more.

list_recordsList recordsA
Read-onlyIdempotent

Records (onboarding requests) across the team, each with status, the client's name and ID, its steps and dates. Filter by status, active/archived, creator, assignee (or unassigned), and the client's visibility; sort by client name or date. Uses GET /records.

Inputs: page, status, sort_by, assignee, +6 more.

list_templatesList templatesA
Read-onlyIdempotent

Templates on the team: record templates (the preset steps, notification, message, reminders and assignees a create_record from that template would use; GET /templates/records), email templates (GET /templates/emails) or document templates (GET /templates/documents). Optionally only enabled or only disabled ones.

Inputs: kind, page, enabled, max_results.

list_webhooksList webhooksA
Read-onlyIdempotent

Webhook subscriptions on the team: delivery URL (origin and path only), subscribed events, enabled flag and dates. The signing secret is never returned. Uses GET /webhooks.

Inputs: page, enabled, max_results.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 9 tools

    Disambiguation5/5

    Each tool has a clearly distinct scope: list_clients/get_client target clients, list_records/list_client_records are differentiated by team-wide vs single-client scope, and get_record/get_check target distinct resources. The list vs get pairs are unambiguous, and the descriptions explicitly clarify boundaries.

    Naming Consistency4/5

    Most tools follow a clean verb_noun pattern (list_clients, get_client, list_records, get_record, get_check, list_templates, list_webhooks). The lone outlier case_status_summary uses a noun-phrase convention, a minor deviation from the otherwise consistent scheme.

    Tool Count5/5

    Nine tools is well-scoped for an onboarding domain, covering clients, records, checks, templates, aggregates and webhooks without redundancy. Each tool earns its place and none feels filler.

    Completeness3/5

    The read surface is broad and coherent, but the server is entirely read-only: there is no create_client, create_record, or decision/approval action despite the domain centering on onboarding workflows and the templates description referencing create_record. These notable missing write operations limit end-to-end lifecycle coverage.

    Maintenance

    ActivitySlowing
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables Claude, ChatGPT and other MCP clients to read NewZapp account details, campaign reports, open heatmaps, contact groups and contact counts, and to search contacts with personal data withheld by default; when writes are enabled, it can also create and update contacts.
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Enables MCP clients such as Claude and ChatGPT to read a Timesheet Portal account, including timesheet time and cost reports, invoices, leave bookings and balances, projects or jobs, clients, users and cost centres, across both the Standard and Recruitment editions. It is read-only, with personal and pay data withheld unless explicitly requested.
      8
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Lets MCP clients such as Claude and ChatGPT read a WorkMobile account's jobs, job types, forms, submitted form records, approvals and mobile users, and, when writes are enabled, create and allocate jobs and approve or reject records.
      10
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Lets Claude, ChatGPT and other MCP clients read events and their sessions, attendances (bookings), forms, form responses, contacts, labels and campaigns from a Gecko student recruitment CRM, and, when writes are enabled, register a contact for an event or add a label to a contact. It is read-only by default and keeps contact data minimization rules.
      MIT