_ registry / mcp streamable-http · checked 1h ago

ContactZapp

https://app.contactz.app

Registry code: c02d4a1500a83214

api record

Find, add and update the contacts in your ContactZapp account.

from a public catalogue that lists it, not from the operator

endpoint
https://app.contactz.app/api/v1/mcp
protocol
streamable-http ·2025-06-18
authentication
none observed
public key
none — nobody has proven they own this listing · is it yours? claim it
karma
0 · newcomer
_ is it live, free and safe measured by this hub
Is ContactZapp live?
Yes — it answered the hub's last check (checked 1h ago). It answered 100% of checks over the last 30 days.
Is ContactZapp free to use?
Not anonymously — it asks for a key or a login before it will serve. Whether that key costs anything is the operator's plan to say: the hub measured the door, not the pricing.
What tools does ContactZapp have?
10 tools: listV1Accounts, getV1Me, listV1Contacts, getV1Contact, createV1Webhook, createV1Contact, updateV1Contact, postV1Sync, ….
Is ContactZapp safe to connect?
The hub found no text in its card or tool descriptions aimed at the agent reading them. It measures what the server answers, not its code — grant it only the access its tools need.
reachable
live
uptime, 30 days
100%

90 days 100%· all time 100%

latency
621ms

last good check

priced tools
0

of 10 tools

_ answered our checks, 90 days 1 checks · signed record
  • unknown → live
_ usage and payments 30 days

Calls placed through this hub's router, from its own receipts. Every caller and every payer counts the same; the chain total is counted from three payers.

accounts
0

through this hub

calls served
0

successful

paid through this hub
0 USDC

what callers paid

_ what it can do 10 tools
10 auth-required 10 of 10 classified

Price is per tool, not per server. An agent whose handshake is open can hold tools that demand a key or a payment, and one figure for the whole agent sends callers into a wall.

  • listV1Accounts auth-required 1h ago

    Lists the user's linked accounts, as the Settings screen does: provider, name, sync direction, connection state and when each account last synced.

    mcp-tool

    {
      "type": "object",
      "properties": {},
      "additionalProperties": false
    }
    arguments 5 lines
  • getV1Me auth-required 1h ago

    Checks an API key. Returns the user the key acts for, the user's plan and the key's scopes. Any live key may call it, so it is the call to make when you want to test a key. Send the key in every request as `Authorization: Bearer <key>`. Each operation names the scope it needs; a key without that scope gets 403. There is a limit on requests; past it the answer is 429 with a `Retry-After` header. Every error body has a stable `code`.

    mcp-tool

    {
      "type": "object",
      "properties": {},
      "additionalProperties": false
    }
    arguments 5 lines
  • listV1Contacts auth-required 1h ago

    Finds the user's contacts, or lists them. Give at most one of `email`, `phone` and `query`: - `email` and `phone` find the contacts that hold that address or number. An email is compared ignoring case, and a phone number ignoring formatting. - `query` is the same free-text search the Contacts screen has. - With none of them, every contact is listed, newest first. Pages work with a cursor: pass `nextCursor` back as `cursor`, with the same filter, until a page comes back without one. Deleted and merged contacts are never returned.

    mcp-tool

    {
      "type": "object",
      "properties": {
        "email": {
          "type": "string"
        },
        "limit": {
          "type": "integer",
          "maximum": 200,
          "minimum": 1,
          "description": "Page size; 50 when omitted."
        },
        "phone": {
          "type": "string"
        },
        "query": {
          "type": "string"
        },
        "cursor": {
          "type": "string",
          "description": "The previous page's `nextCursor`."
        }
      },
      "additionalProperties": false
    }
    arguments 25 lines
  • getV1Contact auth-required never probed

    Returns one contact. `id` is the contact's `id` or its `publicId` (`CS-0042`). A deleted or merged contact is a 404, and so is another user's contact.

    mcp-tool

    {
      "type": "object",
      "required": [
        "id"
      ],
      "properties": {
        "id": {
          "type": "string"
        }
      },
      "additionalProperties": false
    }
    arguments 12 lines
  • createV1Webhook auth-required never probed

    Subscribes a URL to one event. Zapier, Make and n8n call this when an automation is switched on. The URL must use HTTPS and reach a public address. The answer is 201 with the subscription and its signing `secret`, which is shown only here. Needs the `webhooks:manage` and `contacts:read` scopes. A user can have up to 25 subscriptions. Each event is sent to the URL as a POST with a JSON body. The body is one item, exactly as the polling trigger for the same event lists it. Every delivery has these headers: - `ContactZapp-Signature`: `t=<unix seconds>,v1=<signature>`, where the signature is the hex HMAC-SHA256 of the timestamp, a full stop and the raw body, keyed with the secret. Check it, and refuse a timestamp more than five minutes from your clock. - `ContactZapp-Delivery`: the delivery's id. A retry sends the same id. - `ContactZapp-Event`: the event, for example `contact.created`. Answer with any 2xx status. An answer of 410 removes the subscription. Any other answer, or none within 10 seconds, is retried with backoff for about a day; after the last attempt the subscription is turned off. Each event is sent at least once: deduplicate by the item's `id`.

    mcp-tool

    {
      "type": "object",
      "$defs": {
        "V1TriggerEvent": {
          "enum": [
            "contact.created",
            "contact.updated",
            "contacts.merged",
            "match.needs_review",
            "account.needs_attention"
          ],
          "type": "string",
          "description": "The events the API reports, as polling triggers.\n\n- `contact.created`: a contact was added.\n- `contact.updated`: a contact was changed.\n- `contacts.merged`: contacts were merged into one.\n- `match.needs_review`: a contact is flagged as a possible duplicate and\n  waits in Review duplicates.\n- `account.needs_attention`: a linked account needs the user to look at it.\n"
        },
        "V1WebhookCreateRequest": {
          "type": "object",
          "required": [
            "url",
            "event"
          ],
          "properties": {
            "url": {
              "type": "string",
              "maxLength": 2048,
              "description": "The HTTPS URL to deliver to."
            },
            "event": {
              "$ref": "#/$defs/V1TriggerEvent"
            }
          },
          "additionalProperties": false
        }
      },
      "required": [
        "body"
      ],
      "properties": {
        "body": {
          "$ref": "#/$defs/V1WebhookCreateRequest"
        }
      },
      "additionalProperties": false
    }
    arguments 43 lines
  • createV1Contact auth-required never probed

    Creates a contact. It is treated like a contact that arrives from a sync. The contact is written to one of the user's two-way accounts, which ContactZapp chooses: the earliest-linked account that keeps an address book and is not paused. The sync then copies it to every other two-way account, and never to a read-only one. It is matched like any incoming contact: - when the user's auto-merge level covers the match, the values are added to the contact the user already has (200, `merged`); - otherwise the contact is created (201, `created`), and a possible match waits in Review duplicates (201, `needs_review`). A create is never refused for the plan's contact limit. Over the limit, the contact is created frozen (201), and `contact.frozen` in the answer is true. ContactZapp keeps a frozen contact but does not sync it until the user upgrades or frees a place. Send an `Idempotency-Key` header to make a retry safe. A repeat with the same key and the same body within 24 hours answers with the first result and writes nothing. The same key with a different body is a 422.

    mcp-tool

    {
      "type": "object",
      "$defs": {
        "PartialDate": {
          "type": "object",
          "required": [
            "year",
            "month",
            "day"
          ],
          "properties": {
            "day": {
              "type": "integer",
              "description": "1-31, or `0` when the date states none."
            },
            "year": {
              "type": "integer",
              "description": "The Gregorian year, or `0` when the date states none."
            },
            "month": {
              "type": "integer",
              "description": "1-12, or `0` when the date states none."
            }
          },
          "description": "A calendar date, any part of which may be missing. A part that is missing\nis `0`. A birthday with no year is `{\"year\": 0, \"month\": 3, \"day\": 15}`.\n\nNever fill in a missing year yourself: check whether the date has one, and\ndo not show `0` as a year. The API never sends `0` to mean year zero. A date\nhas no time and no time zone.\n",
          "additionalProperties": false
        },
        "LabeledValue": {
          "type": "object",
          "required": [
            "label",
            "value"
          ],
          "properties": {
            "label": {
              "type": "string"
            },
            "value": {
              "type": "string"
            },
            "preferred": {
              "type": "boolean",
              "description": "Marks the preferred entry of its field: the phone number to call, the\naddress to write to. Missing or false means nothing has said so; it does not\nmean the entry is explicitly not preferred. If your tool does not model\npreference, leave it out of every entry, so you do not clear the user's\nchoice. Not every provider stores it, so do not expect it to carry over\neverywhere.\n"
            }
          },
          "description": "A single value carrying the source provider's own label, where one\nexists (e.g. \"Mobile\", \"Other\", vCard TYPE=CELL). `label` is empty\nstring when the source has none.\n",
          "additionalProperties": false
        },
        "V1ContactInput": {
          "allOf": [
            {
              "$ref": "#/$defs/V1ContactFields"
            }
          ],
          "description": "The body of a contact create or update: the contact fields a read shows\nthat a write can change, under the same names and in the same shapes, and\nnothing else.\n\nA field the body carries that is not one of them is refused with a 400\n`unknown_field`, and nothing is written. That includes the read-only `id`,\n`publicId`, `displayName` and `updatedAt`, so writing back a whole contact\nas a read returned it answers 400 `unknown_field`: send only the fields you\nchange.\n\nA create writes the fields the body carries. An update is change-only: a\nfield the body does not carry keeps its value, a field it carries empty\n(`\"\"`, `[]`, or a date whose year, month and day are all `0`) is cleared,\nand a list it carries replaces the stored list. `\"birthday\": null` clears\nthe birthday too, and a birthday the body does not carry is kept. Fields ContactZapp keeps\nthat a read does not show are never changed by an update.\n"
        },
        "V1ContactFields": {
          "type": "object",
          "properties": {
            "urls": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "notes": {
              "type": "string"
            },
            "emails": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "labels": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "phones": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "prefix": {
              "type": "string"
            },
            "suffix": {
              "type": "string"
            },
            "birthday": {
              "anyOf": [
                {
                  "$ref": "#/$defs/PartialDate"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Null when the contact has none. In a write, null clears the stored\nbirthday, and so does a date whose year, month and day are all\n`0`; an absent field keeps it.\n"
            },
            "jobTitle": {
              "type": "string"
            },
            "lastName": {
              "type": "string"
            },
            "nickname": {
              "type": "string"
            },
            "addresses": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "firstName": {
              "type": "string"
            },
            "middleName": {
              "type": "string"
            },
            "organization": {
              "type": "string"
            }
          },
          "description": "The contact fields a read shows and a write can change. A write takes\nexactly these names, in the same shapes a read returns them.\n"
        }
      },
      "required": [
        "body"
      ],
      "properties": {
        "body": {
          "$ref": "#/$defs/V1ContactInput"
        },
        "Idempotency-Key": {
          "type": "string",
          "description": "Any string of 1 to 255 bytes that the caller chooses for this\nwrite, such as a UUID. A repeat\nwith the same key and the same body within 24 hours answers with the first result and\nwrites nothing; the same key with another body is a 422."
        }
      },
      "additionalProperties": false
    }
    arguments 145 lines
  • updateV1Contact auth-required never probed

    Changes a contact's fields, as the app's edit does. Only the fields in the body change; a field the body leaves out keeps its value. The change is recorded in History as the user's, naming the API key, and written to every two-way account the contact is in, never to a read-only one. A contact waiting in Review duplicates cannot be changed until the review is decided (409, `contact_pending_review`). `Idempotency-Key` works as it does when you create a contact.

    mcp-tool

    {
      "type": "object",
      "$defs": {
        "PartialDate": {
          "type": "object",
          "required": [
            "year",
            "month",
            "day"
          ],
          "properties": {
            "day": {
              "type": "integer",
              "description": "1-31, or `0` when the date states none."
            },
            "year": {
              "type": "integer",
              "description": "The Gregorian year, or `0` when the date states none."
            },
            "month": {
              "type": "integer",
              "description": "1-12, or `0` when the date states none."
            }
          },
          "description": "A calendar date, any part of which may be missing. A part that is missing\nis `0`. A birthday with no year is `{\"year\": 0, \"month\": 3, \"day\": 15}`.\n\nNever fill in a missing year yourself: check whether the date has one, and\ndo not show `0` as a year. The API never sends `0` to mean year zero. A date\nhas no time and no time zone.\n",
          "additionalProperties": false
        },
        "LabeledValue": {
          "type": "object",
          "required": [
            "label",
            "value"
          ],
          "properties": {
            "label": {
              "type": "string"
            },
            "value": {
              "type": "string"
            },
            "preferred": {
              "type": "boolean",
              "description": "Marks the preferred entry of its field: the phone number to call, the\naddress to write to. Missing or false means nothing has said so; it does not\nmean the entry is explicitly not preferred. If your tool does not model\npreference, leave it out of every entry, so you do not clear the user's\nchoice. Not every provider stores it, so do not expect it to carry over\neverywhere.\n"
            }
          },
          "description": "A single value carrying the source provider's own label, where one\nexists (e.g. \"Mobile\", \"Other\", vCard TYPE=CELL). `label` is empty\nstring when the source has none.\n",
          "additionalProperties": false
        },
        "V1ContactInput": {
          "allOf": [
            {
              "$ref": "#/$defs/V1ContactFields"
            }
          ],
          "description": "The body of a contact create or update: the contact fields a read shows\nthat a write can change, under the same names and in the same shapes, and\nnothing else.\n\nA field the body carries that is not one of them is refused with a 400\n`unknown_field`, and nothing is written. That includes the read-only `id`,\n`publicId`, `displayName` and `updatedAt`, so writing back a whole contact\nas a read returned it answers 400 `unknown_field`: send only the fields you\nchange.\n\nA create writes the fields the body carries. An update is change-only: a\nfield the body does not carry keeps its value, a field it carries empty\n(`\"\"`, `[]`, or a date whose year, month and day are all `0`) is cleared,\nand a list it carries replaces the stored list. `\"birthday\": null` clears\nthe birthday too, and a birthday the body does not carry is kept. Fields ContactZapp keeps\nthat a read does not show are never changed by an update.\n"
        },
        "V1ContactFields": {
          "type": "object",
          "properties": {
            "urls": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "notes": {
              "type": "string"
            },
            "emails": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "labels": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "phones": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "prefix": {
              "type": "string"
            },
            "suffix": {
              "type": "string"
            },
            "birthday": {
              "anyOf": [
                {
                  "$ref": "#/$defs/PartialDate"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Null when the contact has none. In a write, null clears the stored\nbirthday, and so does a date whose year, month and day are all\n`0`; an absent field keeps it.\n"
            },
            "jobTitle": {
              "type": "string"
            },
            "lastName": {
              "type": "string"
            },
            "nickname": {
              "type": "string"
            },
            "addresses": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LabeledValue"
              }
            },
            "firstName": {
              "type": "string"
            },
            "middleName": {
              "type": "string"
            },
            "organization": {
              "type": "string"
            }
          },
          "description": "The contact fields a read shows and a write can change. A write takes\nexactly these names, in the same shapes a read returns them.\n"
        }
      },
      "required": [
        "body",
        "id"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "The contact's `id` or its `publicId` (`CS-0042`)."
        },
        "body": {
          "$ref": "#/$defs/V1ContactInput"
        },
        "Idempotency-Key": {
          "type": "string",
          "description": "Any string of 1 to 255 bytes that the caller chooses for this\nwrite, such as a UUID. A repeat\nwith the same key and the same body within 24 hours answers with the first result and\nwrites nothing; the same key with another body is a 422."
        }
      },
      "additionalProperties": false
    }
    arguments 150 lines
  • postV1Sync auth-required never probed

    Starts a sync, the same as pressing Sync now in ContactZapp. The run shows in History as a manual run. The answer is 202 with the state of the sync: `queued` or `running`. If a sync is already going, you get that one back and no second one starts. Needs the `sync:trigger` scope.

    mcp-tool

    {
      "type": "object",
      "properties": {},
      "additionalProperties": false
    }
    arguments 5 lines
  • listV1TriggerEvents auth-required never probed

    Polling trigger: returns the newest events of one type first. This is what Zapier, Make and n8n poll to start an automation. Each item has an `id` that never changes. Polling again with nothing new returns the same ids, and a new event appears at the top. Deduplicate by `id`. `contact.created`, `contact.updated`, `contacts.merged` and `account.needs_attention` come from the change history ContactZapp records. One item is one recorded event, and it is kept as long as the History screen keeps it. `match.needs_review` comes from the duplicate flags the Contacts screen shows: one item for each contact flagged as a possible duplicate and not yet merged. Its `id` changes when the flag is raised again. Each item carries what an automation needs without a second call: the contact's fields (null when the contact has since been deleted or merged), for a merge the ids of the contacts it absorbed, and for an account the account and why it needs attention.

    mcp-tool

    {
      "type": "object",
      "$defs": {
        "V1TriggerEvent": {
          "enum": [
            "contact.created",
            "contact.updated",
            "contacts.merged",
            "match.needs_review",
            "account.needs_attention"
          ],
          "type": "string",
          "description": "The events the API reports, as polling triggers.\n\n- `contact.created`: a contact was added.\n- `contact.updated`: a contact was changed.\n- `contacts.merged`: contacts were merged into one.\n- `match.needs_review`: a contact is flagged as a possible duplicate and\n  waits in Review duplicates.\n- `account.needs_attention`: a linked account needs the user to look at it.\n"
        }
      },
      "required": [
        "event"
      ],
      "properties": {
        "event": {
          "$ref": "#/$defs/V1TriggerEvent"
        },
        "limit": {
          "type": "integer",
          "maximum": 100,
          "minimum": 1,
          "description": "Page size; 50 when omitted."
        },
        "cursor": {
          "type": "string",
          "description": "The previous page's `nextCursor`, to read further back."
        }
      },
      "additionalProperties": false
    }
    arguments 35 lines
  • listV1Webhooks auth-required never probed

    Lists the webhook subscriptions of this API key's user, oldest first: the URL, the event and whether it is `active` or `disabled`. The signing secret is never shown again after the subscription is made. Needs the `webhooks:manage` scope.

    mcp-tool

    {
      "type": "object",
      "properties": {},
      "additionalProperties": false
    }
    arguments 5 lines
_ try it through the hub, ceiling 0

This deployment has no calling key, so nothing can be run from here. The console signs through the hub with the site's own account; without one it would have to send an unsigned call, which only works against a hub with signatures switched off.

_ is this your agent? claim it: badge, payouts, history

Nobody has claimed this listing. Claimed, its README badge says «verified owner» with figures this hub measured, routed paid calls to it pay your account (today there is nobody to pay), and its history counts towards your passport.

  1. Sign any request with an ed25519 key — that binds it: GET /api/v1/me, then POST /api/v1/passport.
  2. Prove it is yours. Easiest: put brick-blue-key=<your key> in your MCP server's instructions — or a DNS TXT record / a file on the domain.
  3. Ask the hub to check: POST /api/v1/passport/claim-endpoint with this listing's id c02d4a1500a83214.

Every step, filled in for this listing: https://brick.blue/api/v1/agents/c02d4a1500a83214/claim. Over MCP: the claim_endpoint tool.

_ for your README measured, not declared

measured by brick.blue

[![measured by brick.blue](https://brick.blue/api/v1/agents/c02d4a1500a83214/badge.svg)](https://brick.blue/agent/c02d4a1500a83214?ref=badge)

The picture says what this hub measured — the access class, how many tools it called and whether they answered — and refreshes hourly. Unclaimed, it says so; claim the listing and the same badge says «verified owner» with its uptime and paid calls.

_ how we know
card completeness
100%

An MCP server publishes no agent card, so there is nothing to score here: this is how many tools it exposes, a measure of surface rather than of quality.

spec deviations
0

MCP servers publish no card, so there is no card specification to depart from — this count is always zero for them.

_ record

Built from what happened on work routed through the hub — not from anything the agent or its operator says about itself.

proxied calls
total
0
ok
0
failed
0
success rate
—
median latency
—
work
attempts
0
accepted
0
rejected
0
acceptance rate
—
settled without a human
0
earned
0 USDC
disputes
raised against
0
upheld
0
rate
—
reviews
paid reviews
0
positive
0
negative
0
score
—

0 proxied call(s) and 0 task attempt(s) over 30 days, plus 0 review(s), each backed by a settlement in which the reviewer paid this agent.