Guide

MINT API — Quickstart

Read-only REST API over the CRM messages that Brazilian banks and fintechs send to their customers, as catalogued in MINT by XCORP.

What the API provides

AvailableNot in this version
Messages of the last 30 days (rolling window), CRM BrazilHistory older than 30 days
Counts over 13 months in /v1/stats (by day, institution, tag, product, channel)MCP server, bulk export, webhooks
Filters and full-text search inside the windowSemantic search
The full creative image of each message (image/webp)Original or other image sizes
Yesterday's messages available by 4 p.m. Brasília timeSLA; support is during business hours (Brasília)

There is no send time. A message has a date (YYYY-MM-DD, Brasília) and a seq, its order within the day for that institution. Sort by date, institution, seq if you need an order.

1. Credentials

XCORP gives you one service token per agent: a Client ID and a Client Secret. Send both on every request:

Text
CF-Access-Client-Id: <client id>
CF-Access-Client-Secret: <client secret>

Requests are also restricted to your registered outbound IPs. Keep the secret in a secret store, never in code, prompts or logs. If it leaks, tell XCORP and the token is revoked.

Shell
export MINT_ID='<client id>'
export MINT_SECRET='<client secret>'

2. curl

Check the service (no data, free):

Shell
curl -s https://api.mint.xcorp.cc/v1/health -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"
JSON
{"ok": true, "as_of": "2026-11-08"}

as_of is the date of the last completed data update.

What was sent since yesterday (the main use case; since defaults to yesterday):

Shell
curl -s "https://api.mint.xcorp.cc/v1/feed?insts=nubank,itau" -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"
JSON
{
  "data": [
    {
      "id": "9b1e4c7a2d5f8e0b3a6c9d2e5f8a1b4c",
      "institution": "nubank",
      "date": "2026-11-08",
      "seq": 3,
      "title": "Ganhe cashback no Pix no crédito",
      "channel": "push",
      "tags": ["Cashback", "Pix"],
      "summary": "Offers 2% cashback on Pix paid with the credit card until month end."
    }
  ],
  "next_cursor": null,
  "as_of": "2026-11-08"
}

Search (full text, accent-insensitive, inside the window):

Shell
curl -s "https://api.mint.xcorp.cc/v1/messages?q=cashback&channels=push,email&from=2026-11-01" -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"

One message with all fields, and its image:

Shell
curl -s https://api.mint.xcorp.cc/v1/messages/9b1e4c7a2d5f8e0b3a6c9d2e5f8a1b4c -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"
Shell
curl -s -o cover.webp https://api.mint.xcorp.cc/v1/messages/9b1e4c7a2d5f8e0b3a6c9d2e5f8a1b4c/images/0 -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"

Trend over 13 months, without spending budget:

Shell
curl -s "https://api.mint.xcorp.cc/v1/stats?by=day&from=2025-11-01&insts=nubank" -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"

Your consumption:

Shell
curl -s https://api.mint.xcorp.cc/v1/usage -H "CF-Access-Client-Id: $MINT_ID" -H "CF-Access-Client-Secret: $MINT_SECRET"

3. Python (standard library only)

Python
import json, os, time, urllib.error, urllib.parse, urllib.request

BASE = "https://api.mint.xcorp.cc/v1"
HEADERS = {
    "CF-Access-Client-Id": os.environ["MINT_ID"],
    "CF-Access-Client-Secret": os.environ["MINT_SECRET"],
    "User-Agent": "my-agent/1.0",
}

def get(path, **params):
    url = BASE + path + ("?" + urllib.parse.urlencode(params) if params else "")
    for attempt in range(5):
        try:
            with urllib.request.urlopen(urllib.request.Request(url, headers=HEADERS)) as r:
                return json.load(r), r.headers
        except urllib.error.HTTPError as e:
            try:
                body = json.load(e)
            except ValueError:  # rejected before reaching the API: check credential and IP
                body = {"error": "access", "message": "non-JSON response"}
            if e.code == 429 and (body.get("detail") or {}).get("limit") == "minute":
                time.sleep(int(e.headers.get("retry-after", "60")))
                continue
            raise RuntimeError(f"{e.code} {body['error']}: {body['message']}")
    raise RuntimeError("still rate limited")

def feed(since=None, **filters):
    """All pages of /v1/feed. Same parameters on every page, plus the cursor."""
    params = {k: v for k, v in {"since": since, **filters}.items() if v}
    while True:
        page, headers = get("/feed", **params)
        print("charged now:", headers["x-records-new"], "budget left:", headers["x-budget-remaining"])
        yield from page["data"]
        if not page["next_cursor"]:
            return
        params["cursor"] = page["next_cursor"]

for card in feed(insts="nubank,itau"):
    print(card["date"], card["seq"], card["institution"], card["title"])

4. Pagination

Lists return up to limit items (1 to 50, default 50) and a next_cursor. To get the next page, repeat the same filters and dates and add cursor=<next_cursor> (limit and view may change). A cursor is valid for 1 hour, only for your credential, those filters and the dates they resolved to: if you rely on the default since/from, a day change in Brasília invalidates it. Otherwise you get 400 bad_cursor and should restart without a cursor. next_cursor is null on the last page.

A to after today is treated as today; from after to returns an empty list. Unknown or repeated parameters return 400 bad_request with detail = {param, why}; /v1/messages/{id} accepts no parameters (it is always the full view).

5. Budget and limits

Your access plan sets the limits below and the end date of your access; GET /v1/usage shows them.

6. Errors

All errors are {"error": "<code>", "message": "<what to do>", "detail": ...}.

StatuserrorMeaning
400bad_requestInvalid, unknown or repeated parameter; detail = {param, why}
400windowDate before the 30-day window; detail has the first accepted date. Use /v1/stats for older trends
400bad_cursorCursor tampered, expired, from another credential or other filters
403forbiddenMissing or unknown credential
403disabled / access_endedYour access is disabled or its period has ended. Do not retry; contact XCORP
404not_foundMessage missing, out of the window or not visible to you; or unknown slug or id in a filter (all look the same)
405method_not_allowedOnly GET (HEAD and OPTIONS too return 405)
429rate_limitedSee limits above
500internalUnexpected error; retry later
503disabledService temporarily disabled for everyone; retry after retry-after seconds

Quote the x-request-id header when contacting support.