← Home

Genus CodesAPI Documentation

Python examples for interacting with the Genus Codes API

Base URL: https://genuscodes.com
All endpoints accept and return JSON unless otherwise noted.

Authentication

All API endpoints require authentication via a Bearer token. Generate a token from your Account page.

GET Authorization: Bearer gc_...

Include this header with every request.

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

# Use headers with every request
response = requests.post(
    "https://genuscodes.com/search/function",
    json={"function_hash": "..."},
    headers=headers
)
Keep your token secret. Do not commit tokens to source control. Store them in environment variables or a config file.

1. Search for a Function

POST /search/function

Search for binaries containing a specific function by its UUID5 hash.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

# Function hash (UUID5 format)
function_hash = "e935d66e-4777-579f-9278-e4c6d955b79b"

response = requests.post(
    "https://genuscodes.com/search/function",
    json={
        "function_hash": function_hash,
        "limit": 100  # Optional: max results
    },
    headers=headers
)

response.raise_for_status()
data = response.json()
print(f"Found in {data.get('total_binaries', 0)} binaries")

Response

{
    "function_hash": "e935d66e-4777-579f-9278-e4c6d955b79b",
    "total_binaries": 52,
    "binaries": [
        {"sha256": "abc123...", "malicious": true, "source": "VirusShare_00001"},
        {"sha256": "def456...", "malicious": false, "source": "Win10"}
    ],
    "from_cache": true
}

2. Search for a Binary

POST /search/binary

Search for a binary by its SHA256 hash and find similar binaries.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

sha256 = "b99d61d874728edc0918ca0eb10eab93d381e7367e377406e65963366c874450"

response = requests.post(
    "https://genuscodes.com/search/binary",
    json={
        "hash_value": sha256,
        "hash_type": "sha256"
    },
    headers=headers
)

response.raise_for_status()
data = response.json()

# If processing, poll for results
if data.get("status") == "processing":
    job_id = data["job_id"]
    print(f"Job submitted: {job_id}")
    # Poll /job/{job_id} for completion

3. Submit a Binary for Analysis

POST /submit/binary

Submit a binary (PE, ELF, or Mach-O) or a password-protected zip/7z archive for analysis. Validates, extracts (if archive), stores, and queues for processing in one step.

Parameters (multipart form)

Submit a raw binary

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

with open("sample.exe", "rb") as f:
    response = requests.post(
        "https://genuscodes.com/submit/binary",
        files={"file": ("sample.exe", f)},
        headers=headers
    )

if not response.ok:
    print(f"Error: {response.status_code} - {response.text}")
else:
    data = response.json()
    if data["status"] == "duplicate":
        print(f"Already exists: {data['results_url']}")
    elif data["status"] == "submitted":
        print(f"Submitted! SHA256: {data['sha256']}")
        print(f"Results: {data['results_url']}")

Submit an encrypted zip/7z

# Default password "infected" is tried automatically
with open("malware.zip", "rb") as f:
    response = requests.post(
        "https://genuscodes.com/submit/binary",
        files={"file": ("malware.zip", f)},
        headers=headers
    )

# With a custom password
with open("malware.7z", "rb") as f:
    response = requests.post(
        "https://genuscodes.com/submit/binary",
        files={"file": ("malware.7z", f)},
        data={"password": "mypassword"},
        headers=headers
    )
Archive rules: Must contain exactly one file. Supported formats: zip (AES + legacy encryption) and 7z. Max file size after extraction: 100MB.

Response (submitted)

{
    "status": "submitted",
    "job_id": "a1b2c3d4-...",
    "sha256": "b99d61d874728edc...",
    "results_url": "/results/b99d61d874728edc..."
}

Response (password required)

{
    "status": "password_required",
    "message": "Archive is encrypted. Provide a password."
}

4. Check Job Status

GET /job/{job_id}

Check the status of an async job.

Request

import requests
import time

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

job_id = "your-job-id-here"

# Poll until complete
while True:
    response = requests.get(f"https://genuscodes.com/job/{job_id}", headers=headers)
    data = response.json()

    status = data.get("status")
    print(f"Status: {status}")

    if status in ["completed", "failed"]:
        break

    time.sleep(5)  # Wait 5 seconds between polls

print(f"Result: {data}")

5. Get Binary Analysis Results

GET /results/{sha256}

View analysis results for a binary (HTML page).

This endpoint returns an HTML page. For programmatic access, use:

GET /api/binary/{sha256}/results

Get analysis results as JSON.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

sha256 = "b99d61d874728edc0918ca0eb10eab93d381e7367e377406e65963366c874450"

response = requests.get(
    f"https://genuscodes.com/api/binary/{sha256}/results",
    headers=headers
)

response.raise_for_status()
data = response.json()

print(f"Total functions: {data['summary']['total_functions']}")
print(f"Unique functions: {data['summary']['unique_functions']}")
print(f"Malicious matches: {data['summary']['functions_with_malicious_matches']}")

# Global tags (admin-curated labels, e.g. malware family names)
print(f"Global tags: {data['binary'].get('global_tags', [])}")

# Binary similarity
similarity = data.get("similarity", {})
for threshold, info in similarity.get("thresholds", {}).items():
    print(f"{threshold}: {info['count']} similar binaries")

6. Get Function Ratios

GET /api/binary/{hash}/ratio

How much of a binary's code is unique, benign, or malicious — in one call. Accepts md5, sha1, sha256, or sha512. Hash type is detected automatically.

Included on every plan: 20/day free, 50/day researcher, 150/day pro, unlimited enterprise. Resets at midnight UTC.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

sha256 = "b99d61d874728edc0918ca0eb10eab93d381e7367e377406e65963366c874450"

response = requests.get(
    f"https://genuscodes.com/api/binary/{sha256}/ratio",
    headers=headers
)

response.raise_for_status()
data = response.json()

strict = data["strict"]
print(f"Functions: {data['function_count']}")
print(f"Malicious: {strict['malicious_ratio']:.1%}")
print(f"Unique:    {strict['unique_ratio']:.1%}")

Response

{
    "sha256": "b99d61d8...",
    "malicious": true,
    "computed": true,
    "computed_at": "2026-09-25T03:15:43",
    "function_count": 3923,
    "strict": {
        "unique": 3204,
        "benign_only": 1,
        "malicious_only": 545,
        "both": 173,
        "unique_ratio": 0.8167,
        "benign_ratio": 0.0443,
        "malicious_ratio": 0.1830
    },
    "normalized": { "...": "same shape, matched on normalized hashes" }
}

strict matches functions byte-for-byte. normalized collapses compiler variants, so it usually finds more. The four buckets sum to function_count. Benign and malicious ratios both include both, so they overlap and will not sum to 1.

A binary labeled benign reads 100% benign regardless of what its functions share. computed: false means we have the binary but have not scored it yet — distinct from 404, which means it is not in the corpus. A 404 still consumes one lookup.

7. Check Usage & Quota

GET /billing/usage

Check your current plan, usage counts, and remaining quota.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

response = requests.get(
    "https://genuscodes.com/billing/usage",
    headers=headers
)

response.raise_for_status()
data = response.json()
print(f"Plan: {data['plan']}")
print(f"Binary ops: {data['binary_ops']} / {data['binary_limit']} ({data['binary_remaining']} remaining)")
print(f"Function ops: {data['function_ops']} / {data['function_limit']} ({data['function_remaining']} remaining)")
print(f"Ratio ops: {data['ratio_ops']} / {data['ratio_limit']} ({data['ratio_remaining']} remaining today)")

Response

{
    "plan": "free",
    "binary_ops": 12,
    "binary_limit": 20,
    "binary_remaining": 8,
    "function_ops": 1500,
    "function_limit": 14400,
    "function_remaining": 12900,
    "ratio_ops": 3,
    "ratio_limit": 20,
    "ratio_remaining": 17,
    "is_lifetime": true,
    "status": "active"
}

is_lifetime applies to binary and function counts only. Ratio lookups are always a daily allowance, so ratio_ops is today's count on every plan.

8. Track Fetch Progress

GET /api/binary/{sha256}/progress

Track the progress of function fetching for a binary analysis.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

sha256 = "b99d61d874728edc0918ca0eb10eab93d381e7367e377406e65963366c874450"

response = requests.get(
    f"https://genuscodes.com/api/binary/{sha256}/progress",
    headers=headers
)

response.raise_for_status()
data = response.json()
print(f"Progress: {data['fetched']} / {data['total']}")

Response

{
    "fetched": 150,
    "total": 500
}

9. Refresh Binary Cache

POST /api/binary/{sha256}/refresh-cache

Re-run analysis for a binary to pick up newly indexed samples. Subject to a cooldown period.

Request

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}"}

sha256 = "b99d61d874728edc0918ca0eb10eab93d381e7367e377406e65963366c874450"

response = requests.post(
    f"https://genuscodes.com/api/binary/{sha256}/refresh-cache",
    headers=headers
)

response.raise_for_status()
data = response.json()
print(f"Status: {data['status']}")

Response (success)

{
    "status": "refreshed",
    "message": "Binary analysis has been refreshed"
}

Response (cooldown)

{
    "status": "too_recent",
    "message": "Binary was analyzed recently. Please wait before refreshing."
}

10. Health Check

GET /health

Check server health and service connectivity. No authentication required.

Request

import requests

response = requests.get("https://genuscodes.com/health")
data = response.json()
print(f"Status: {data['status']}")

Response

{
    "status": "healthy"
}

11. Research — Search by Tag or Filename

GET /research

Search binaries by global tag, user tag, or filename. Returns up to 200 results. Authentication required for searches; stats page is public.

Query Parameters

Search by Global Tag

import requests

response = requests.get(
    "https://genuscodes.com/research",
    params={"type": "global_tag", "q": "mirai"},
    headers={"Authorization": "Bearer gc_YOUR_TOKEN"}
)

Search by Filename

response = requests.get(
    "https://genuscodes.com/research",
    params={"type": "filename", "q": "calc.exe"},
    headers={"Authorization": "Bearer gc_YOUR_TOKEN"}
)

Search by User Tag

response = requests.get(
    "https://genuscodes.com/research",
    params={"type": "user_tag", "q": "lockbit"},
    headers={"Authorization": "Bearer gc_YOUR_TOKEN"}
)
Note: Global tag search matches the global_tags array on each binary. Filename search uses case-insensitive partial matching. Web submissions are excluded from results.

JSON

GET /api/research?type={type}&q={query}

Same search, returned as JSON. No quota; 30 requests/minute.

response = requests.get(
    "https://genuscodes.com/api/research",
    params={"type": "global_tag", "q": "mirai"},
    headers={"Authorization": "Bearer gc_YOUR_TOKEN"}
)
for b in response.json()["results"]:
    print(b["sha256"], b["malicious"], b["global_tags"])
{
    "type": "global_tag",
    "query": "mirai",
    "count": 200,
    "truncated": true,
    "results": [
        {"sha256": "7e1fd898...", "name": "...", "malicious": true,
         "source": "Sample Collection", "filetype": "ELF", "cpu": "arm",
         "global_tags": ["mirai"]}
    ]
}

Tags are 2–64 characters of a-z 0-9 . _ : + -, starting with a letter or digit. Filename queries are 3–128 characters. user_tag results also carry your user_tags and only ever include binaries you annotated.

12. Function Tags

Get Global Function Tags (Batch)

POST /api/function-tags-batch

Retrieve admin-curated global tags for a list of function hashes. Returns tags for functions that have been labeled (e.g., "crypto_aes", "c2_beacon").

import requests

API_TOKEN = "gc_your_token_here"
headers = {"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"}

response = requests.post(
    "https://genuscodes.com/api/function-tags-batch",
    json={"func_hashes": [
        "ad57bfc0-33e4-502e-adf1-ee76738aa0c7",
        "ffa8519c-d90c-506c-8986-20ad012fccc8"
    ]},
    headers=headers
)

response.raise_for_status()
data = response.json()
for func_hash, tags in data["tags"].items():
    print(f"{func_hash}: {tags}")

Response

{
    "tags": {
        "ad57bfc0-33e4-502e-adf1-ee76738aa0c7": ["crypto_aes", "c2_beacon"],
        "ffa8519c-d90c-506c-8986-20ad012fccc8": ["packer_upx"]
    }
}

Update User Function Tags

POST /api/function/{func_hash}/tags

Set your personal tags on a function. Max 8 tags, alphanumeric/hyphens/underscores only, max 50 chars each.

func_hash = "ad57bfc0-33e4-502e-adf1-ee76738aa0c7"

response = requests.post(
    f"https://genuscodes.com/api/function/{func_hash}/tags",
    json={"tags": ["my_label", "suspicious"], "mode": "add"},
    headers=headers
)

Response

{
    "status": "ok",
    "tags": ["my_label", "suspicious"]
}

mode: "replace" (default) sets the list to exactly the tags sent, so sending [] clears them. "add" merges with the tags you already have and never removes any. Use "add" from scripts and agents, so tags set on the website are kept. The 8-tag cap applies to the merged list. The response is always the full resulting list.

Read Your Tags and Notes

GET /api/binary/{sha256}/annotations

Your own tags and notes on a binary. Returns empty values (not 404) when you haven't annotated it.

GET /api/function/{func_hash}/annotations

Your own tags on a function.

{"sha256": "...", "tags": ["lockbit"], "notes": "packed with UPX"}

These only ever return the caller's annotations. Read before adding with "mode": "add", since the 8-tag cap applies to the combined list.

Update Your Binary Tags

POST /api/recents/tags

Set your personal tags on a binary. These are searchable with /api/research?type=user_tag. Same rules and mode as function tags.

response = requests.post(
    "https://genuscodes.com/api/recents/tags",
    json={"sha256": sha256, "tags": ["lockbit"], "mode": "add"},
    headers=headers
)

13. Projects

Group binaries into projects with notes. Every project call only ever sees your own projects; another user's project id returns 404. Project ids are 32 hex characters.

GET/api/projects

Your projects: id, name, binary_count, dates.

POST/api/projects

{"name": "..."} (1–100 chars) returns {"project_id"}. 409 if the name exists.

GET/api/projects/{project_id}

One project: name, notes, and member binaries (sha256s).

POST/api/projects/{project_id}/binaries

{"sha256": "..."} returns {"added": true|false} (false if already present). Max 1,000 per project.

POST/api/projects/{project_id}/notes

{"notes": "...", "mode": "append"}. Max 10,000 chars.

POST/api/recents/notes

Your notes on a binary: {"sha256": "...", "notes": "...", "mode": "append"}. Max 1,000 chars. Read them back with /api/binary/{sha256}/annotations.

GET/api/projects/{project_id}/export

The project as Markdown.

pid = requests.post("https://genuscodes.com/api/projects",
                    json={"name": "cs-cluster"}, headers=headers).json()["project_id"]
requests.post(f"https://genuscodes.com/api/projects/{pid}/binaries",
              json={"sha256": sha256}, headers=headers)
requests.post(f"https://genuscodes.com/api/projects/{pid}/notes",
              json={"notes": "shares 93-binary function group", "mode": "append"}, headers=headers)

Notes mode (both notes endpoints): "replace" (default) sets the notes to exactly what you send. "append" adds your text on a new line after the existing notes, on the server, so nothing written in between is lost. Over the limit is rejected with 400; notes are never truncated.

Error Handling

The API returns standard HTTP status codes:

try:
    response = requests.post(...)
    response.raise_for_status()  # Raise exception for 4xx/5xx
    data = response.json()
except requests.exceptions.HTTPError as e:
    print(f"HTTP Error: {e.response.status_code}")
    print(f"Details: {e.response.text}")