Skip to main content
Code exposure finds public code that mentions your organization. You give it keywords such as acme.com or an internal host name. It searches public code platforms for them and stores each match as a finding with a risk score, so you can read the riskiest ones first. In the dashboard this is Exposure → Code Exposure. In the API reference the endpoints sit under the Code Scan tag, and the plan feature is Exposed Code Detection. Scans started in either place share one history and one allowance. There are two kinds of scan:
  • Keyword scan. Searches one source for your keywords. One request can start a keyword scan on up to five sources at once.
  • Contributor scan. Lists the developers who contribute to the GitHub organizations and GitLab groups or projects you name.
Both run in the background: start the scan, poll it until it ends, then read or export what it found.

Availability and cost

Availability: Exposed Code Detection. Every endpoint on this page needs it, including lists, results, exports and templates. Plans and quotas lists what each plan includes. Cost:
  • Keyword scans. 1 unit of your monthly Code Exposure Keywords allowance for each unique keyword in a start request, however many sources search it. Keywords are compared without regard to case, so acme.com and ACME.com count once.
  • Contributor scans. 1 unit of your monthly Code Exposure Contributors allowance for each scan.
The units are taken before any scan starts, all or nothing: if the allowance cannot cover every unique keyword, nothing starts and nothing is taken. A keyword start is refunded only when no source starts. Once one source starts, the call keeps every unit, including those for keywords whose source landed in failures. A contributor start is refunded on any non-2xx response. Scans that end failed, and scans you delete, keep their units. Polling, results, exports, lists and templates use no units. Without the feature, or when your license is not active, every endpoint on this page returns 403:
When the month’s allowance cannot cover the request, the start call returns 429 and nothing starts:
A contributor start returns the same shape with "resource": "code_exposure_contributor_search" and Code Exposure Contributor Search in detail. limit is your monthly total, packs included. Retrying fails until the allowance resets on the 1st of the month or a pack is added. A keyword start checks its body before the allowance, so an invalid body returns 400 and uses nothing. A contributor start checks the allowance first: once it is spent, every contributor start returns 429, even one with an invalid body. A 429 whose body reads "message": "Rate limit exceeded" is the per-second rate limit or the API Calls allowance, not a Code Exposure allowance. See Rate limits.

Sources

Each keyword scan searches one source. Name it in source.

How keywords match

A finding is kept only when your keyword appears in its text: the file content, snippet, title, file name, description or URL. Matching ignores case. Gist is the exception: it keeps what the Gist search returns for the operators below. On github and google, search_type sets how strict the match is: gitlab always matches the exact phrase. On gist, operator turns the keyword into a Gist search filter: search_type and operator are not checked when you start a scan. Send one of the values above.

Start keyword scans

POST /api/code_scan/keyword_scans/ takes a sources list. Each entry starts one keyword scan.
This request uses 2 units: acme.com and acme-internal. The same keyword on GitLab and Postman counts once. The body rules:
  • sources: 1 to 5 entries, each source at most once.
  • keywords: at least one per source, and exactly one for gitlab. A keyword may appear only once per source. Blank keywords are refused, and spaces around a keyword are trimmed.
  • options: optional, an object of settings for that source. See Source options. Keywords never go inside options.
The call returns 201 with the scans that started and the sources that did not:
Keep each scan’s id. parameters shows the options and keywords the scan runs with. Times are UTC. Always read failures. A source in it did not start, and its keywords are still counted. Start it again after the cause clears, which uses units again. If no source starts at all, the call returns the first failure’s status and body instead of 201, and every unit is refunded.

Source options

Options are not checked when you start a scan. A misspelled name is ignored and the scan runs with that option’s default.

GitHub

GitLab

GitLab reads each file from the project’s main branch, and every finding’s url points to that branch. A project whose default branch has another name returns few or no findings.
A GitLab scan without options.projects still starts and uses its unit. It then ends failed with "error": "projects or users parameter is required". Always send at least one project or user.

Gist

Google

Postman

Follow a scan

Fetch each scan with GET /api/code_scan/keyword_scans/{scan_id}/ until status is finished or failed. Poll every 30 seconds or slower.
status takes four values:
  • queued: waiting to run.
  • running: searching. Findings are saved while it runs, so you can read them already.
  • finished: done. It may have found nothing.
  • failed: done, with an error in error. Findings saved before the error are kept. The platform does not retry it. Start a new scan to try again, which uses units again.
result_count is the number of findings. The scan list fills it in. For a single scan, read total from its results.

Read the findings

GET /api/code_scan/keyword_scans/{scan_id}/results/ returns one scan’s findings, riskiest first.
  • page starts at 1. per_page defaults to 20, max 100. A page past the end returns an empty items list.
  • sort_by takes risk_score (the default), created_at or id. sort_order takes desc (the default) or asc. Ties are broken by id.
  • created_from and created_to (ISO 8601) keep findings saved in that window.

What each finding gives you

  • data: the finding itself. The fields depend on the source. Every source gives keyword, url and snippet.
    • github: repo, path, title, language, file_size and commit_date (any when you set no since or until).
    • gitlab: project and path.
    • gist: gist_id, username, filename and description.
    • google: title, site (the site searched), platform and content_type.
    • postman: collection_name, username and description.
  • data.risk_score: how risky the finding looks, from 0 to 10. Higher is riskier. It is the higher of two scores: the pattern score and the AI assessment. On GitHub, GitLab and Gist findings, each match of a known secret format, such as a private key, cloud access key or API token, adds 2 to 4 points to the pattern score. A Postman finding scores 1 when its collection text mentions an API, a key or a token. The dashboard marks 7 and above in red and 4 to 6 in yellow. A finding without a score sorts as 0.
  • data.ai_risk_score and data.ai_rationale: the AI assessment of the snippet, as a score from 0 to 10 and a short text that explains it. Every finding with a snippet is assessed, on every source, Google and Postman included. GitHub, GitLab, Gist and Postman findings always carry both fields, with 0 and an empty text when no assessment ran. Google findings carry them only when one ran. The dashboard shows ai_rationale as the risk explanation.
  • data.patterns_matched: the secret patterns found in the snippet, each with a rule and a description. On Postman, the api-related rule is not a secret format. It marks a collection whose text mentions an API, a key or a token.
  • result_hash: identifies the file, gist, collection or page. Two findings with the same hash, in any of your scans, are the same item. GitLab is the exception: its hash covers only the file path, so the same path in two projects, such as README.md, shares a result_hash and a first_seen_at. Compare data.project as well before you treat two GitLab findings as one.
  • first_seen_at: when your organization’s scans first found this item. A date older than the scan means an earlier scan already found it.
  • status: new on every finding.

Export the findings

GET /api/code_scan/keyword_scans/{scan_id}/export/ downloads every finding of one scan as a file. Choose the format with file_format: csv (the default), json or xml.
Use file_format, never format. The API reserves format for itself: ?format=csv returns 404 and ?format=json is ignored, so you get CSV.
  • The file is named job_<scan id>_results.<format> in the Content-Disposition header.
  • Findings are in the order they were saved, not by risk.
  • Each record has id, module and created_at, then the data fields flattened into dotted keys such as patterns_matched[0].rule. module holds the scanner’s name for the source, such as github_search.
  • Every data value is written as a string, in all three formats.
  • In XML, id, module and created_at are attributes of each result element. In keys, dots and opening brackets become underscores and closing brackets are dropped, so patterns_matched[0].rule becomes patterns_matched_0_rule.
  • A scan with no findings returns 404 with {"detail": "No results found for this job"}.
Leave the Accept header out, or send */* as curl does. An Accept: text/csv header returns 406.

List and delete scans

GET /api/code_scan/keyword_scans/ lists your organization’s keyword scans, newest first, including the ones started in the dashboard.
  • page starts at 1. per_page defaults to 10, max 100.
  • status filters on one status, such as running. source filters on one source.
  • created_from and created_to (ISO 8601) keep scans created in that window.
  • The page has the same envelope as the results: total, page, per_page, total_pages and items.
DELETE /api/code_scan/keyword_scans/{scan_id}/ removes a scan and all of its findings. It returns 204 with no body and refunds no units. Deleting a running scan stops it.

Find contributors

A contributor scan lists the developers behind an organization’s public repositories. Use their user names as users in a GitHub keyword scan, or as projects entries in a GitLab one, to search code they publish under their own accounts.
1

Start the scan

POST /api/code_scan/contributor_scans/ takes custom_links: a list of full URLs.
  • GitHub: a GitHub organization URL, such as https://github.com/acme. The scan reads the contributors of its first 100 public repositories, skips forks and leaves out bots.
  • GitLab: a gitlab.com group or project URL. A group scan reads up to 20 of its projects.
  • Each entry must be a full URL, scheme included. Links to other hosts are ignored.
Send custom_links. Without it, the scan falls back to GitHub and GitLab links stored for your organization. The API cannot set those, and with none stored the call returns 400 and uses no unit.The call returns 201:
The id is a string, not a number. URL-encode it when you put it in a path.
2

Poll until it ends

Fetch the scan with GET /api/code_scan/contributor_scans/{scan_id}/ until status is SUCCESS or FAILURE. Contributor statuses are uppercase:
  • PENDING: waiting to run.
  • RUNNING: scanning. progress moves from 0 to 100 and message says what the scan is doing.
  • SUCCESS: done. Read result.
  • FAILURE: done, with the reason in error.
3

Read the contributors

On SUCCESS, result lists the developers found:
  • unique_contributors: one entry per developer, merged across all links. On GitHub, repo_count counts the public repositories the developer owns or is a member of, up to 50. On GitLab, it counts the matched account’s other projects, profile_url is null, and username is the commit author’s name when no GitLab account matches. GitLab matches an account by searching the commit email, then the author name, and takes the first hit.
  • contributors_discovered: how many developers the scan found.
  • success: false when the scan found nobody. The status is still SUCCESS.
result also carries link_results, with the detail for each link.
GET /api/code_scan/contributor_scans/ lists your contributor scans, newest first. per_page defaults to 50, max 100. It filters on status (uppercase) and on created_from and created_to. The status filter uses the last status the platform recorded: a scan moves from PENDING to RUNNING only when it is fetched while it runs, so status=RUNNING can miss a running scan. Fetch a scan by its id for its live status. Contributor scans cannot be deleted.

Save settings as a template

A template stores a named set of scan settings for your organization. You can create templates and list them. POST /api/code_scan/templates/ takes three fields:
  • template_name: required. Names are unique within your organization.
  • template_type: scan (the default) or report.
  • settings: required. Any JSON object, stored as you send it.
The call returns 201 with the template’s id, template_name, template_type, settings, created_at and updated_at. GET /api/code_scan/templates/ lists templates newest first. It filters on template_type, created_from and created_to, and per_page defaults to 20. A start request cannot name a template. To run a template, read its settings and send them in a start request. Templates saved in the dashboard appear in this list too, and their settings use the dashboard’s own format, not the start request body. Templates cannot be changed or deleted through the API.

Limits

  • 10 active keyword scans. Your organization can have at most 10 keyword scans queued or running at once, dashboard scans included. Each source in a start request is one scan. A source over the limit lands in failures with status_code 429, and its keywords are still counted. To avoid that, list your queued and running scans first and keep their total plus the sources you send at 10 or fewer.
  • Starts per minute. Your organization can start up to 10 keyword scans and 2 contributor scans per minute. The two limits are counted separately. Each source in a start request is one keyword scan, so two requests with five sources each use the whole minute. A keyword source over that limit lands in failures with 429 and an error field that starts with Rate limit exceeded. A contributor start over it returns that 429 and uses no unit. Start again a minute later.
  • A busy scanner. When the scanning queue is full, a source fails with 502. Start it again later.
  • Findings per keyword. GitHub keeps up to 1,000, Google 300, Gist 50 and Postman 50 by default. GitLab keeps up to 100 for the whole scan. Google can pass its cap by up to 99, as cap explains. Change them with the source options.
  • Trailing slash. Every path ends with /. A path without it returns a 301 redirect, and a POST or DELETE sent to it is not carried out.
  • No schedules. The API has no scheduled scans. To scan on a schedule, start scans from your own scheduler.

Errors

Errors explains every body the API returns.

Endpoints