Signal Number Lookup API: Check Registration & Identity

Published August 23, 2026 Β· UpdatedOctober 9, 2026

Signal is the messenger of choice for privacy-conscious users, and its audience keeps growing well beyond that niche. If your product or research touches phone numbers, knowing whether a number is on Signal, and being able to resolve a stable identifier for it, shapes how you route messages and enrich contacts. A Signal number lookup API answers both questions over plain HTTP.

This guide covers the three Signal operations, shows how to read their results correctly in Python, and explains where a lookup like this fits next to the other messaging checks on the platform.

Step 1: Get an API key

Sign up and copy an active key from your Dashboard. Free credits are included, so you can test every call below without paying.

Step 2: Check registration

The check operation is the lightest call. It takes a phone number in international format and returns only the number, its normalized e164 form and registered. You can pass the number with or without the leading +; the API normalizes it to digits.

curl "https://api.opendata-api.com/api/project/signal/check?key=YOUR_API_KEY&number=15551234567"

registered is a three-state value, and handling all three states is the difference between a reliable pipeline and a flaky one:

  • true: the number is a registered Signal account.
  • false: the number is not registered.
  • null: the answer could not be determined, which is transient (for example a timeout). The message field says why. Treat it as "try again later", never as "not on Signal".

Step 3: Resolve the identity

The user-info operation does a full lookup for one number. When the number is registered it returns the account's public identity: the ACI (Account Identifier, a UUID) and the PNI (Phone Number Identifier, usually written PNI:<uuid>), plus optional plaintext metadata such as identityKey, unrestrictedUnidentifiedAccess, capabilities and badges when available. It also returns a machine-readable status (for example found, not_found or timeout), a human-readable message and durationMs.

curl "https://api.opendata-api.com/api/project/signal/user-info?key=YOUR_API_KEY&number=15551234567"

Pay attention to fieldsEncrypted. When it is true, Signal is withholding the display name, about text and avatar because they are end-to-end encrypted with a profile key that only the person's contacts hold. That is a privacy property of Signal itself, not a gap in the lookup, so do not build features that promise a name or photo for strangers.

Step 4: Refresh metadata by ACI

If you already stored an ACI, the profile operation fetches the public profile metadata for it directly, without resolving the phone number again. It returns only plaintext fields: identityKey, unidentifiedAccess, unrestrictedUnidentifiedAccess, capabilities, badges and uuid (the same value as the ACI). Name, about and avatar are never present on this path for non-contacts.

curl "https://api.opendata-api.com/api/project/signal/profile?key=YOUR_API_KEY&aci=3bc3a597-297d-4f8c-821a-13aaa9ede5ea"

A complete Python example

This helper runs the cheap check first, retries only the undetermined results, and calls user-info only for numbers that are confirmed registered:

import time
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.opendata-api.com/api/project/signal"


def get(operation, **params):
    params["key"] = API_KEY
    resp = requests.get(f"{BASE}/{operation}", params=params, timeout=30)
    resp.raise_for_status()
    body = resp.json()
    # A reply can be wrapped more than once as {"code": ..., "data": {...}}
    while isinstance(body, dict) and "code" in body and isinstance(body.get("data"), (dict, list)):
        body = body["data"]
    return body


def lookup(number, retries=2):
    for attempt in range(retries + 1):
        data = get("check", number=number)
        if data.get("registered") is True:
            return {"number": number, "registered": True, "identity": get("user-info", number=number)}
        if data.get("registered") is False:
            return {"number": number, "registered": False}
        time.sleep(2 * (attempt + 1))  # null: transient, back off and retry
    return {"number": number, "registered": None, "reason": data.get("message")}


result = lookup("15551234567")
print(result)
if result["registered"]:
    info = result["identity"]
    print("names withheld by Signal:", info.get("fieldsEncrypted"))

Only the three-state registered value drives the control flow here. Everything else is stored as returned, so a field that is absent for one account does not break the loop.

Signal compared with the other lookups

Each messenger exposes a different amount of public information, so the right operation depends on what you need to decide:

QuestionBest fit
Is this number on Signal at all?check on Signal
Do I need a stable per-user identifier?user-info (ACI/PNI)
Is the number on Viber?the Viber number lookup
Is it on Telegram, and under which handle?the Telegram number checker
Which of several channels should I use?the lead verification workflow

Because Signal withholds profile details from non-contacts by design, treat this lookup as a registration and identity signal rather than a profile-enrichment source.

Use cases

  • Channel routing: offer Signal as a contact option only where the number is actually registered.
  • Contact enrichment: attach Signal presence and the ACI/PNI identifiers to existing phone records.
  • Identity resolution: use the ACI and PNI to deduplicate and link records.
  • Risk and trust signals: combine registration status and capabilities with your own fraud checks.
  • List segmentation: split audiences by which secure messengers they use.

Best practices

  • Normalize numbers first. Strip spaces and punctuation and include the country code. A leading + is accepted.
  • Check before you enrich. Run check for the whole list and call user-info only on registered = true, so unregistered numbers never trigger the heavier lookup.
  • Cache by number. Store the result with a timestamp and re-check on a schedule, not on every page view.
  • Never convert null into false. An undetermined result is not a verdict; queue it for a retry.
  • Keep the ACI, not just the number. People change phone numbers, and the ACI lets you re-fetch metadata with profile later.
  • Stay compliant. Process only numbers you have a lawful basis to use, and respect the privacy expectations of a privacy-first platform.

FAQ

How do I check if a phone number is on Signal? Call the check operation with the number in international format. The response contains registered as true, false or null. A null means the answer was undetermined for now, so retry later instead of treating the number as unregistered.

Can I get a Signal user's name or photo from a phone number? No. Signal encrypts display name, about text and avatar end to end with a profile key that only a person's contacts hold. For strangers the lookup returns fieldsEncrypted=true and withholds those fields.

What are ACI and PNI? The ACI is the account's stable UUID, and the PNI is the identifier tied to its phone number. Both are stable identifiers that you can use to deduplicate and link records.

Does the number need a plus sign? No. You can send the full international number with or without the leading +, and the API normalizes it to digits. Always include the country code.

Should I call user-info for every number? Usually not. Run the lighter check first and call user-info only for numbers that are registered, because that is the only case where there is an identity to return.

Try it

Test lookups on the Signal Number Validation page, or read the check, user-info and profile references. See pricing for per-request costs.