Moatgator Public API

Version 1.0.0 · REST API for CI, scripts and integrations.

The public API v1 of Moatgator. This file is the source of truth: the server validates requests and (outside production) responses against it, src/publicapi/types.gen.ts is generated from it, and the reference page is built from it.

Authentication

Send an API key as Authorization: Bearer mg_live_... (or mg_test_...). A key belongs to one app and carries scopes (builds:write, builds:read, scans:read, threats:read, attest:verify); ask only for what the integration needs. For CI there is no key to store: a GitHub Actions job exchanges its own OIDC token for a short-lived access token (POST /v1/auth/oidc, valid 10 minutes) and sends it as Authorization: Bearer <token> in place of the key. The app must have a trust rule that names the repository and ref (or environment) allowed to do so; with no rule, nobody gets in. Access tokens never carry device scopes (attest:verify, events:ingest).

Errors

Errors use RFC 9457 application/problem+json. code is a stable machine-readable identifier; detail is for people.

Rate limits

Per API key, by the plan of the app. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets); a 429 also carries Retry-After.

Idempotency

POST operations that create something accept an Idempotency-Key header (8 to 128 characters). Repeating the same key with the same body within 24 hours returns the original response (with Idempotent-Replayed: true); the same key with a different body is a 422.

Pagination

List endpoints use opaque cursors: pass next_cursor from the previous page as cursor. Never build or parse a cursor.

Versioning

The major version is in the path (/v1). Additive changes (new fields, new endpoints) are not breaking. Deprecated operations send Deprecation and Sunset headers at least 6 months before removal. See docs/api/versioning.md.

Auth

POST/v1/auth/oidc

Exchange a CI OIDC token for an access token · No API key (signed URL)

In a GitHub Actions job with permissions: id-token: write, request an OIDC token with audience equal to this API's base URL and send it here with the id of the app. If a trust rule of the app matches the token (issuer, repository, ref or environment), the response is a short-lived access token (10 minutes) with the scopes granted by the matching rules. An OIDC token can be exchanged once. The same 401 answers every refusal (unknown app, no rule, wrong repository, expired, replayed token): the reason is recorded in the organization's audit trail, not revealed to the caller.

Request body application/json

FieldTypeDescription
token requiredstringThe OIDC token issued to the CI job.
appId requiredstring (pattern ^app_[A-Za-z0-9_-]{6,40}$)

Responses

StatusMeaningBody
200The access token.OidcExchangeResponse
400Malformed request.Problem
401Missing, malformed, revoked or expired API key.Problem
429Rate limit exceeded. See `Retry-After`.Problem

GET/v1/whoami

Who am I · API key

Identifies the credential (API key or OIDC access token) without needing any scope: the app it belongs to, the plan, the scopes and when it expires. Used by moatgator whoami and moatgator doctor. Never returns the secret.

Responses

StatusMeaningBody
200The credential.Whoami
401Missing, malformed, revoked or expired API key.Problem
429Rate limit exceeded. See `Retry-After`.Problem

Builds

POST/v1/builds

Register a build · API key

Registers a build of the app that owns the API key. Declare the artifact (sizeBytes, sha256) to receive a short-lived signed uploadUrl for the file. Requires scope builds:write.

Parameters

NameInType
Idempotency-Keyheaderstring (pattern ^[A-Za-z0-9_.:-]+$)

Request body application/json

FieldTypeDescription
platform requiredPlatform
version requiredstring
commitstring | null
artifactArtifactDeclaration
{
  "platform": "android",
  "version": "3.4.1",
  "commit": "9fceb02",
  "artifact": {
    "sizeBytes": 48211023,
    "sha256": "3f9c1a7e5b2d4086a1c3e5f70912b4d63f9c1a7e5b2d4086a1c3e5f70912b4d6"
  }
}

Responses

StatusMeaningBody
201Build registered.Build
400Malformed request.Problem
401Missing, malformed, revoked or expired API key.Problem
403The key lacks the required scope, or the signed URL is invalid or expired.Problem
409Conflicts with the current state (for example an idempotent request still in progress).Problem
422Well-formed but invalid (validation failed, or the idempotency key was reused with another body).Problem
429Rate limit exceeded. See `Retry-After`.Problem

GET/v1/builds/{id}

Get a build · API key

Requires scope builds:read. A build of another app is a 404.

Parameters

NameInType
id requiredpathstring (pattern ^bld_[A-Za-z0-9_-]{6,40}$)

Responses

StatusMeaningBody
200The build.Build
401Missing, malformed, revoked or expired API key.Problem
403The key lacks the required scope, or the signed URL is invalid or expired.Problem
404The resource does not exist (or belongs to another app).Problem
429Rate limit exceeded. See `Retry-After`.Problem

PUT/v1/uploads/{token}

Upload a build artifact · No API key (signed URL)

Uploads the file declared when the build was created, as the raw request body (application/octet-stream). The URL is the signed uploadUrl returned by createBuild; the token in the path is the credential, so it needs no Authorization header and expires in 15 minutes. The size and SHA-256 must match what was declared.

Parameters

NameInType
token requiredpathstring

Request body application/octet-stream

Responses

StatusMeaningBody
200Artifact stored.Build
400Malformed request.Problem
403The key lacks the required scope, or the signed URL is invalid or expired.Problem
404The resource does not exist (or belongs to another app).Problem
409Conflicts with the current state (for example an idempotent request still in progress).Problem
413The body is larger than allowed.Problem
422Well-formed but invalid (validation failed, or the idempotency key was reused with another body).Problem

Threats

GET/v1/apps/{id}/threats

List security events of an app · API key

Newest first. Requires scope threats:read; the key must belong to this app. Device identifiers are never returned.

Parameters

NameInType
id requiredpathstring
limitqueryinteger (min 1, max 100)
cursorquerystring
detectionquerystring: root_jailbreak, emulator, debugger, hooking, tamper, overlay, accessibility_abuse, screen_capture, remote_access

Responses

StatusMeaningBody
200A page of events.ThreatList
400Malformed request.Problem
401Missing, malformed, revoked or expired API key.Problem
403The key lacks the required scope, or the signed URL is invalid or expired.Problem
404The resource does not exist (or belongs to another app).Problem
429Rate limit exceeded. See `Retry-After`.Problem

Scans

GET/v1/scans/{id}

Get a scan · API key

Requires scope scans:read. A scan of another app is a 404.

Parameters

NameInType
id requiredpathstring (pattern ^scn_[A-Za-z0-9_-]{6,40}$)

Responses

StatusMeaningBody
200The scan.Scan
401Missing, malformed, revoked or expired API key.Problem
403The key lacks the required scope, or the signed URL is invalid or expired.Problem
404The resource does not exist (or belongs to another app).Problem
429Rate limit exceeded. See `Retry-After`.Problem

Attest

POST/v1/attest/verify

Verify an attestation token · API key

Checks the attestation token an app sent to your backend. Requires scope attest:verify. The attestation backend is a stub in this version (engine: stub): the contract is stable, the verdicts are not yet backed by real platform checks.

Parameters

NameInType
Idempotency-Keyheaderstring (pattern ^[A-Za-z0-9_.:-]+$)

Request body application/json

FieldTypeDescription
token requiredstring
nonce requiredstring
platform requiredPlatform

Responses

StatusMeaningBody
200The verdict.AttestResult
400Malformed request.Problem
401Missing, malformed, revoked or expired API key.Problem
403The key lacks the required scope, or the signed URL is invalid or expired.Problem
422Well-formed but invalid (validation failed, or the idempotency key was reused with another body).Problem
429Rate limit exceeded. See `Retry-After`.Problem

Schemas

Problem

RFC 9457 problem details.

FieldTypeDescription
type requiredstring (format uri)
title requiredstring
status requiredinteger (min 400, max 599)
detailstring
instancestring
code requiredstringStable machine-readable identifier.
errorsarray of objectField-level validation errors.
retryAfterinteger (min 1)Seconds to wait (rate limits).

Platform

string: android, ios

Sha256

string (pattern ^[0-9a-f]{64}$)

ArtifactDeclaration

FieldTypeDescription
sizeBytes requiredinteger (min 1, max 524288000)
sha256 requiredSha256

BuildCreate

FieldTypeDescription
platform requiredPlatform
version requiredstring
commitstring | null
artifactArtifactDeclaration

Artifact

FieldTypeDescription
status requiredstring: pending, uploaded
sizeBytes requiredinteger
sha256 requiredSha256
uploadUrlstring (format uri)Signed URL to PUT the file to; present while `status` is `pending`.
uploadUrlExpiresAtstring (format date-time)

Build

FieldTypeDescription
id requiredstring (pattern ^bld_[A-Za-z0-9_-]{6,40}$)
appId requiredstring
platform requiredPlatform
version requiredstring
commitstring | null
status requiredstring: succeeded
engine requiredstring: previewBuilds are recorded in preview mode (no signed artifact pipeline yet).
createdAt requiredstring (format date-time)
artifact requirednull | Artifact

Threat

FieldTypeDescription
id requiredstring
detection requiredstring: root_jailbreak, emulator, debugger, hooking, tamper, overlay, accessibility_abuse, screen_capture, remote_access
verdict requiredstring: clean, suspicious, compromised
confidence requirednumber (min 0, max 1)
action requiredstring: monitor, warn, block, end_session
signals requiredarray of string
platformnull | Platform
appVersionstring | null
buildIdstring | null
occurredAt requiredstring (format date-time)
count requiredinteger (min 1)How many identical events were merged into this one.

ThreatList

FieldTypeDescription
data requiredarray of Threat
hasMore requiredboolean
nextCursor requiredstring | nullPass as `cursor` to get the next page; null on the last page.

Finding

FieldTypeDescription
id requiredstring
severity requiredstring: low, medium, high, critical
title requiredstring
detail requiredstring
fix requiredstring

Scan

FieldTypeDescription
id requiredstring (pattern ^scn_[A-Za-z0-9_-]{6,40}$)
appId requiredstring
engine requiredstring: preview, static`static` = analysis of an uploaded package; `preview` = rules over declared metadata.
grade requiredstring (pattern ^[A-F][+-]?$)
score requiredinteger (min 0, max 100)
target requiredobject
findings requiredarray of Finding
createdAt requiredstring (format date-time)

Whoami

FieldTypeDescription
appId requiredstring
plan requiredstring: hatchling, indie, startup, scale, enterprise
scopes requiredarray of string
auth requiredstring: api_key, oidc
expiresAt requiredstring | null (format date-time)

OidcExchangeRequest

FieldTypeDescription
token requiredstringThe OIDC token issued to the CI job.
appId requiredstring (pattern ^app_[A-Za-z0-9_-]{6,40}$)

OidcExchangeResponse

FieldTypeDescription
accessToken requiredstringSend as `Authorization: Bearer <accessToken>`. Never log it.
tokenType requiredstring: Bearer
expiresIn requiredintegerSeconds until the token expires.
scopes requiredarray of string: builds:write, builds:read, scans:read, threats:read
appId requiredstring

AttestRequest

FieldTypeDescription
token requiredstring
nonce requiredstring
platform requiredPlatform

AttestResult

FieldTypeDescription
valid requiredboolean
verdict requiredstring: trusted, suspicious, untrusted
reasons requiredarray of string
engine requiredstring: stubThe attestation backend is a stub in this version.