Référence de l’API

Lisez les notes, constats et changements de vos domaines, et lancez des audits, depuis vos scripts, votre intégration continue, vos outils d’automatisation et vos assistants d’IA. À partir de l’offre Solo.

Version 1.0.0 · Spécification OpenAPI 3.1 (JSON)

Pour commencer

L’API répond à l’adresse https://app.perimetry.cloud. Créez une clé dans Réglages › Clés d’API (propriétaires et administrateurs), puis envoyez-la dans l’en-tête Authorization de chaque requête, jamais dans l’adresse :

Authorization: Bearer pc_…

Une clé en lecture seule suffit à tout sauf au lancement d’audits, qui demande une clé avec le droit audit. Créer et révoquer des clés : Clés d’API.

Vérifier une clé :

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" https://app.perimetry.cloud/api/v1/me

Les domaines par identifiant ou par nom

Partout où un chemin prend {id}, donnez l’identifiant du domaine ou son nom (example.com). Un domaine d’un autre espace répond 404, comme un domaine absent.

Versions

Chaque réponse porte Perimetry-Api-Version: 1. La version 1 ne fait que gagner des champs : rien n’est renommé ni retiré. Ignorez les champs que vous ne connaissez pas.

La spécification

La même description, en OpenAPI 3.1, pour Postman, Insomnia, les générateurs de code et les outils d’automatisation. Publique, sans clé : https://app.perimetry.cloud/api/v1/openapi.json. Cette page est construite à partir d’elle.

Sans code

Des connecteurs pour n8n, Zapier et Make se préparent sur cette API. En attendant leur publication, le module de requête HTTP de chaque outil fonctionne avec les exemples ci-dessous ; pour être prévenu dès qu’un audit se termine, ajoutez l’adresse webhook de votre scénario comme canal Webhook dans la page Alertes : elle reçoit le résumé de chaque audit en JSON.

Limites

Les appels sont comptés pour tout l’espace, par heure d’horloge :

OffreAppels par heure
Solo300
Agency1 200
Agency Plus3 600

Chaque réponse authentifiée porte X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset (heure Unix de l’heure suivante). Au-delà de la limite, l’API répond 429 avec Retry-After. Après soixante requêtes avec une mauvaise clé en quinze minutes depuis un même réseau, les suivantes sont refusées : attendez quinze minutes.

Les audits à la demande suivent la règle du bouton Auditer maintenant (un par heure et par domaine avec Solo) et, par l’API, un toutes les 15 minutes par domaine.

Erreurs

Les erreurs répondent en JSON avec une phrase (en anglais) et un code stable :

{"error": "invalid API key", "code": "unauthorized"}
StatutCodeSignification
400invalid_parameterUn paramètre a une valeur que l’API n’accepte pas ; le message indique les valeurs possibles.
401unauthorizedLa clé manque, est mal formée, révoquée ou inconnue.
403plan_excludes_apiL’offre de l’espace ne comprend pas l’API.
403insufficient_scopeLa clé ne peut pas lancer d’audit : créez-en une avec le droit audit.
404not_foundAbsent de cet espace (un domaine d’un autre espace répond de même).
404no_auditLe domaine n’a pas encore d’audit.
409not_verifiedUn audit a été demandé pour un domaine non prouvé.
429rate_limitedLa limite horaire est atteinte : attendez Retry-After secondes.
429too_many_failuresTrop de requêtes avec une mauvaise clé depuis votre réseau : quinze minutes d’attente.
429audit_too_soonUn audit à la demande de ce domaine a eu lieu trop récemment : attendez Retry-After secondes.
500internalUn problème de notre côté.
503unavailableL’audit n’a pas pu être mis en file : réessayez.
507storage_quotaL’espace a utilisé le stockage de son offre : supprimez d’anciennes données ou passez à une offre plus grande.
507storage_fullNotre stockage est plein pour le moment : réessayez plus tard.

Compte

La clé et son espace.

GET /api/v1/me

La clé, son espace et sa limite horaire

L’espace et l’offre de la clé, ce qu’elle peut faire (ses droits) et ce qui reste de la limite horaire. Un bon premier appel pour vérifier une clé.

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/me"

Réponse 200

{
  "apiVersion": 1,
  "workspace": {
    "name": "Acme",
    "plan": "agency"
  },
  "key": {
    "name": "GitHub Actions",
    "prefix": "pc_3kQx",
    "scopes": [
      "read",
      "audit"
    ]
  },
  "rateLimit": {
    "limit": 1200,
    "remaining": 1187,
    "resetAt": "2026-11-12T10:00:00.000Z"
  }
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
workspaceobject
workspace.namestring | nullName of the workspace.
workspace.planstringPlan of the workspace: solo, agency, agency-plus.
keyobject
key.namestringName given to the key.
key.prefixstringFirst characters of the key.
key.scopesread | audit[]read: read results; audit: also start audits.
rateLimitobject
rateLimit.limitintegerCalls allowed per clock hour, for the whole workspace.
rateLimit.remainingintegerCalls left this hour (this one counted).
rateLimit.resetAtstring (date-time)When the next hour starts.

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

Domaines

Les domaines de l’espace.

GET /api/v1/domains

Lister les domaines

Chaque domaine de l’espace avec son état, son client, sa note, ses constats ouverts par gravité, les dates du dernier et du prochain audit et un lien vers sa page.

Paramètres

NomOùTypeObligatoireDescription
namerequêtestringnonNe garder que le domaine de ce nom (example.com).

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains"

Réponse 200

{
  "apiVersion": 1,
  "domains": [
    {
      "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
      "name": "example.com",
      "status": "verified",
      "verified": true,
      "client": null,
      "score": 72,
      "grade": "C",
      "counts": {
        "critical": 0,
        "high": 2,
        "medium": 3,
        "low": 4,
        "info": 6
      },
      "lastAuditAt": "2026-11-12T03:14:07.000Z",
      "nextAuditAt": "2026-11-19T03:14:07.000Z",
      "attention": true,
      "links": {
        "app": "https://app.perimetry.cloud/domains/3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
        "api": "/api/v1/domains/3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10"
      }
    }
  ]
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
domainsobject[]
domains[].idstring (uuid)Id of the domain.
domains[].namestringName of the domain, such as example.com.
domains[].statuspending | verified | lost | pausedpending: ownership not proved yet; verified: proved and audited; lost: the proof disappeared; paused: audits stopped.
domains[].verifiedbooleanTrue when the status is verified.
domains[].clientstring | nullClient the domain belongs to (agency plans), or null.
domains[].scoreinteger | nullScore of the last audit, out of 100; null before the first audit.
domains[].gradestring | nullGrade of the last audit, A to F.
domains[].counts{ severity: integer } | nullOpen findings of the last audit by severity.
domains[].lastAuditAtstring (date-time) | nullWhen the last audit started.
domains[].nextAuditAtstring (date-time) | nullWhen the next scheduled audit is due.
domains[].attentionboolean | nullTrue when the last audit found something new and serious.
domains[].linksobject
domains[].links.appstring (uri)The domain’s page in the application.
domains[].links.apistringThis domain in the API.

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

GET /api/v1/domains/{id}

Un domaine et son dernier audit

Le domaine, son dernier audit avec l’évolution de la note depuis le précédent, le nombre de constats nouveaux et corrigés, et les trois constats à corriger en premier.

Paramètres

NomOùTypeObligatoireDescription
idcheminstringouiIdentifiant du domaine, ou son nom (example.com).
langrequêteen | fr | denonLangue des titres, explications et corrections des constats (majuscules ou minuscules). Toute autre valeur donne l’anglais. (par défaut en)

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains/example.com"

Réponse 200

{
  "apiVersion": 1,
  "domain": {
    "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
    "name": "example.com",
    "status": "verified",
    "verified": true,
    "client": null,
    "score": 72,
    "grade": "C",
    "counts": {
      "critical": 0,
      "high": 2,
      "medium": 3,
      "low": 4,
      "info": 6
    },
    "lastAuditAt": "2026-11-12T03:14:07.000Z",
    "nextAuditAt": "2026-11-19T03:14:07.000Z",
    "attention": true,
    "links": {
      "app": "https://app.perimetry.cloud/domains/3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
      "api": "/api/v1/domains/3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10"
    }
  },
  "lastAudit": {
    "id": "8b1d5e2f-7c3a-4f6e-b2d9-0a4c6e8f1a23",
    "startedAt": "2026-11-12T03:14:07.000Z",
    "finishedAt": "2026-11-12T03:16:52.000Z",
    "trigger": "schedule",
    "scannerVersion": "0.13.1",
    "score": 72,
    "grade": "C",
    "counts": {
      "critical": 0,
      "high": 2,
      "medium": 3,
      "low": 4,
      "info": 6
    },
    "attention": true,
    "previous": {
      "id": "c4e7a1b9-2d5f-4a8c-9e3b-6f1a2d4c8e57",
      "startedAt": "2026-11-05T03:14:07.000Z",
      "score": 80,
      "grade": "B"
    },
    "scoreDelta": -8,
    "newFindings": 2,
    "fixedFindings": 1
  },
  "doFirst": [
    {
      "fp": "5d0e3b7c1a9f4e26",
      "id": "email.dmarc.p_none",
      "severity": "high",
      "title": "DMARC policy only monitors (p=none)",
      "target": "example.com"
    }
  ]
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
domainobject
domain.idstring (uuid)Id of the domain.
domain.namestringName of the domain, such as example.com.
domain.statuspending | verified | lost | pausedpending: ownership not proved yet; verified: proved and audited; lost: the proof disappeared; paused: audits stopped.
domain.verifiedbooleanTrue when the status is verified.
domain.clientstring | nullClient the domain belongs to (agency plans), or null.
domain.scoreinteger | nullScore of the last audit, out of 100; null before the first audit.
domain.gradestring | nullGrade of the last audit, A to F.
domain.counts{ severity: integer } | nullOpen findings of the last audit by severity.
domain.lastAuditAtstring (date-time) | nullWhen the last audit started.
domain.nextAuditAtstring (date-time) | nullWhen the next scheduled audit is due.
domain.attentionboolean | nullTrue when the last audit found something new and serious.
domain.linksobject
domain.links.appstring (uri)The domain’s page in the application.
domain.links.apistringThis domain in the API.
lastAuditobject | nullNull before the first audit.
lastAudit.idstring (uuid)
lastAudit.startedAtstring (date-time)
lastAudit.finishedAtstring (date-time) | null
lastAudit.triggerstringschedule or manual.
lastAudit.scannerVersionstringVersion of the scanner.
lastAudit.scoreinteger
lastAudit.gradestring
lastAudit.counts{ severity: integer }Open findings by severity (a severity with none may be left out).
lastAudit.attentionbooleanSomething new and serious.
lastAudit.previousobject | null
lastAudit.previous.idstring (uuid)Id of the audit.
lastAudit.previous.startedAtstring (date-time) | nullWhen the audit started.
lastAudit.previous.scoreintegerScore out of 100.
lastAudit.previous.gradestringGrade, A to F.
lastAudit.scoreDeltainteger | nullChange of score since the previous audit.
lastAudit.newFindingsinteger | nullFindings that were not in the previous audit.
lastAudit.fixedFindingsinteger | nullFindings of the previous audit that are gone.
doFirstobject[]The (at most) three findings to fix first.
doFirst[].fpstring | nullFingerprint: the same check on the same target keeps the same fingerprint from one audit to the next.
doFirst[].idstringId of the check, such as email.dmarc.missing.
doFirst[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
doFirst[].titlestringTitle, in the language asked for.
doFirst[].targetstringHost, address or record the finding is about.

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 404 Absent de cet espace (code not_found).
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

Constats

Ce que le dernier audit a trouvé.

GET /api/v1/domains/{id}/findings

Constats ouverts du dernier audit

Les constats ouverts du dernier audit, du plus grave au plus léger, chacun avec sa correction, les enregistrements DNS prêts à copier et les exigences de conformité qu’il concerne. Les constats acceptés sur demande.

Paramètres

NomOùTypeObligatoireDescription
idcheminstringouiIdentifiant du domaine, ou son nom (example.com).
min_severityrequêtecritical | high | medium | low | infononGarder les constats de cette gravité et plus graves (majuscules ou minuscules). Toute autre valeur répond 400 invalid_parameter. Sans lui : tous. (par défaut info)
include_acceptedrequête1 | truenon1 ou true : renvoyer aussi les constats acceptés, dans accepted.
limitrequêteintegernonAu plus ce nombre de constats (et autant de constats acceptés). (par défaut 500)
langrequêteen | fr | denonLangue des titres, explications et corrections des constats (majuscules ou minuscules). Toute autre valeur donne l’anglais. (par défaut en)

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains/example.com/findings?min_severity=high&lang=fr"

Réponse 200

{
  "apiVersion": 1,
  "domain": {
    "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
    "name": "example.com"
  },
  "audit": {
    "id": "8b1d5e2f-7c3a-4f6e-b2d9-0a4c6e8f1a23",
    "startedAt": "2026-11-12T03:14:07.000Z",
    "score": 72,
    "grade": "C",
    "counts": {
      "critical": 0,
      "high": 2,
      "medium": 3,
      "low": 4,
      "info": 6
    }
  },
  "total": 1,
  "findings": [
    {
      "fp": "5d0e3b7c1a9f4e26",
      "id": "email.dmarc.p_none",
      "severity": "high",
      "category": "email",
      "target": "example.com",
      "title": "DMARC policy only monitors (p=none)",
      "detail": "Receivers are told to deliver mail that fails DMARC anyway: anyone can still send mail in the domain’s name.",
      "evidence": "v=DMARC1; p=none; rua=mailto:dmarc@example.com",
      "fix": "Move the policy to quarantine, then reject, once the reports show every sender is aligned.",
      "references": [
        "https://www.rfc-editor.org/rfc/rfc7489"
      ],
      "records": [
        {
          "type": "TXT",
          "name": "_dmarc.example.com",
          "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com",
          "replaces": "v=DMARC1; p=none; rua=mailto:dmarc@example.com",
          "note": "One step stricter: mail that fails DMARC goes to spam."
        }
      ],
      "compliance": [
        "NIS2 Art. 21(2)(j)",
        "ISO 27001 A.8.21"
      ]
    }
  ]
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
domainobject
domain.idstring (uuid)Id of the domain.
domain.namestringName of the domain.
auditobject
audit.idstring (uuid)
audit.startedAtstring (date-time)
audit.scoreinteger
audit.gradestring
audit.counts{ severity: integer }Open findings by severity (a severity with none may be left out).
totalintegerOpen findings at or above min_severity, before limit.
findingsobject[]Open findings, worst first.
findings[].fpstring | nullFingerprint, stable from one audit to the next.
findings[].idstringId of the check.
findings[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
findings[].categorystringemail, web, dns, exposure…
findings[].targetstringHost, address or record the finding is about.
findings[].titlestringTitle, in the language asked for.
findings[].detailstring | nullWhy it matters.
findings[].evidencestring | nullWhat the audit saw.
findings[].fixstring | nullHow to fix it.
findings[].referencesstring (uri)[]Standards and documentation.
findings[].recordsobject[]DNS records ready to copy that fix it, when the audit allows (always empty for an accepted finding).
findings[].records[].typestringTXT, CAA, MX… or "file" for a file to serve (MTA-STS policy).
findings[].records[].namestringName of the record (or address of the file).
findings[].records[].valuestringValue to publish.
findings[].records[].replaces?string | nullThe current value this one replaces, when there is one.
findings[].records[].note?stringWhat to check before publishing.
findings[].compliancestring[]NIS2, Cyber Essentials and ISO 27001 requirements the finding relates to.
findings[].acceptedReason?string | nullAccepted findings only: why the risk was accepted.
findings[].acceptedUntil?string | nullAccepted findings only: until when, or null for no end date.
accepted?object[]Accepted findings (with include_accepted only), with the reason and end date.
accepted[].fpstring | nullFingerprint, stable from one audit to the next.
accepted[].idstringId of the check.
accepted[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
accepted[].categorystringemail, web, dns, exposure…
accepted[].targetstringHost, address or record the finding is about.
accepted[].titlestringTitle, in the language asked for.
accepted[].detailstring | nullWhy it matters.
accepted[].evidencestring | nullWhat the audit saw.
accepted[].fixstring | nullHow to fix it.
accepted[].referencesstring (uri)[]Standards and documentation.
accepted[].recordsobject[]DNS records ready to copy that fix it, when the audit allows (always empty for an accepted finding).
accepted[].records[].typestringTXT, CAA, MX… or "file" for a file to serve (MTA-STS policy).
accepted[].records[].namestringName of the record (or address of the file).
accepted[].records[].valuestringValue to publish.
accepted[].records[].replaces?string | nullThe current value this one replaces, when there is one.
accepted[].records[].note?stringWhat to check before publishing.
accepted[].compliancestring[]NIS2, Cyber Essentials and ISO 27001 requirements the finding relates to.
accepted[].acceptedReason?string | nullAccepted findings only: why the risk was accepted.
accepted[].acceptedUntil?string | nullAccepted findings only: until when, or null for no end date.

Erreurs

  • 400 Un paramètre a une valeur que l’API n’accepte pas (code invalid_parameter) ; le message nomme le paramètre et les valeurs possibles.
  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 404 Absent de cet espace (not_found), ou le domaine n’a pas encore d’audit (no_audit).
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

Audits

Historique, comparaison, dernier audit et audits à la demande.

GET /api/v1/domains/{id}/compare

Ce qui a changé depuis l’audit précédent

Note avant et après, constats ajoutés, corrigés ou changés de gravité, et les changements de l’infrastructure : hôtes, ports, certificats, domaines sosies, services cloud.

Paramètres

NomOùTypeObligatoireDescription
idcheminstringouiIdentifiant du domaine, ou son nom (example.com).
langrequêteen | fr | denonLangue des titres, explications et corrections des constats (majuscules ou minuscules). Toute autre valeur donne l’anglais. (par défaut en)

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains/example.com/compare"

Réponse 200

{
  "apiVersion": 1,
  "domain": {
    "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
    "name": "example.com"
  },
  "audit": {
    "id": "8b1d5e2f-7c3a-4f6e-b2d9-0a4c6e8f1a23",
    "startedAt": "2026-11-12T03:14:07.000Z",
    "score": 72,
    "grade": "C"
  },
  "previous": {
    "id": "c4e7a1b9-2d5f-4a8c-9e3b-6f1a2d4c8e57",
    "startedAt": "2026-11-05T03:14:07.000Z",
    "score": 80,
    "grade": "B"
  },
  "comparison": {
    "score": {
      "from": 80,
      "to": 72,
      "delta": -8
    },
    "attention": true,
    "worstAdded": "high",
    "added": [
      {
        "fp": "5d0e3b7c1a9f4e26",
        "id": "email.dmarc.p_none",
        "severity": "high",
        "title": "DMARC policy only monitors (p=none)",
        "target": "example.com"
      }
    ],
    "resolved": [],
    "severityChanged": [],
    "infra": {
      "newHosts": [
        "staging.example.com"
      ],
      "goneHosts": [],
      "newLookalikes": [],
      "newCloud": [],
      "ports": [
        {
          "ip": "192.0.2.10",
          "opened": [
            8080
          ],
          "closed": []
        }
      ],
      "certificates": [],
      "newCertificates": [
        {
          "issuer": "Let's Encrypt",
          "names": [
            "staging.example.com"
          ],
          "notBefore": "2026-11-10T00:00:00.000Z"
        }
      ]
    }
  }
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
domainobject
domain.idstring (uuid)Id of the domain.
domain.namestringName of the domain.
auditobject
audit.idstring (uuid)
audit.startedAtstring (date-time)
audit.scoreinteger
audit.gradestring
previousobject | nullNull at the first audit.
previous.idstring (uuid) | null
previous.startedAtstring (date-time) | null
previous.scoreinteger
previous.gradestring | null
comparisonobject | nullNull at the first audit.
comparison.scoreobject
comparison.score.frominteger
comparison.score.tointeger
comparison.score.deltainteger
comparison.attentionbooleanSomething new and serious.
comparison.worstAddedstring | nullWorst severity among the new findings.
comparison.addedobject[]
comparison.added[].fpstring | nullFingerprint: the same check on the same target keeps the same fingerprint from one audit to the next.
comparison.added[].idstringId of the check, such as email.dmarc.missing.
comparison.added[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
comparison.added[].titlestringTitle, in the language asked for.
comparison.added[].targetstringHost, address or record the finding is about.
comparison.resolvedobject[]
comparison.resolved[].fpstring | nullFingerprint: the same check on the same target keeps the same fingerprint from one audit to the next.
comparison.resolved[].idstringId of the check, such as email.dmarc.missing.
comparison.resolved[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
comparison.resolved[].titlestringTitle, in the language asked for.
comparison.resolved[].targetstringHost, address or record the finding is about.
comparison.severityChangedobject[]
comparison.severityChanged[].fpstring | nullFingerprint: the same check on the same target keeps the same fingerprint from one audit to the next.
comparison.severityChanged[].idstringId of the check, such as email.dmarc.missing.
comparison.severityChanged[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
comparison.severityChanged[].titlestringTitle, in the language asked for.
comparison.severityChanged[].targetstringHost, address or record the finding is about.
comparison.severityChanged[].fromstringSeverity before.
comparison.severityChanged[].tostringSeverity now.
comparison.infraobject
comparison.infra.newHostsstring[]
comparison.infra.goneHostsstring[]
comparison.infra.newLookalikesstring[]
comparison.infra.newCloudstring[]
comparison.infra.portsobject[]
comparison.infra.ports[].ipstring
comparison.infra.ports[].openedinteger[]
comparison.infra.ports[].closedinteger[]
comparison.infra.certificatesobject[]Certificates that changed.
comparison.infra.certificates[].hoststring
comparison.infra.certificates[].fromstring
comparison.infra.certificates[].tostring
comparison.infra.newCertificatesobject[]New certificates seen in the transparency logs.
comparison.infra.newCertificates[].issuerstring
comparison.infra.newCertificates[].namesstring[]
comparison.infra.newCertificates[].notBeforestring

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 404 Absent de cet espace (not_found), ou le domaine n’a pas encore d’audit (no_audit).
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

GET /api/v1/domains/{id}/audits

Historique des audits

Les audits du domaine, du plus récent au plus ancien : date, déclenchement, note, constats par gravité.

Paramètres

NomOùTypeObligatoireDescription
idcheminstringouiIdentifiant du domaine, ou son nom (example.com).
limitrequêteintegernonAu plus ce nombre d’audits. (par défaut 30)

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains/example.com/audits?limit=10"

Réponse 200

{
  "apiVersion": 1,
  "domain": {
    "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
    "name": "example.com"
  },
  "audits": [
    {
      "id": "8b1d5e2f-7c3a-4f6e-b2d9-0a4c6e8f1a23",
      "startedAt": "2026-11-12T03:14:07.000Z",
      "trigger": "schedule",
      "score": 72,
      "grade": "C",
      "counts": {
        "high": 2,
        "medium": 3,
        "low": 4,
        "info": 6
      },
      "attention": true
    }
  ]
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
domainobject
domain.idstring (uuid)Id of the domain.
domain.namestringName of the domain.
auditsobject[]Newest first.
audits[].idstring (uuid)
audits[].startedAtstring (date-time)
audits[].triggerstring
audits[].scoreinteger
audits[].gradestring
audits[].counts{ severity: integer }Open findings by severity (a severity with none may be left out).
audits[].attentionboolean

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 404 Absent de cet espace (code not_found).
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

GET /api/v1/domains/{id}/latest

Le dernier audit complet, ou en SARIF

Le dernier audit tel qu’il est enregistré, avec ses constats et les constats acceptés, en anglais ; ou, avec format=sarif, un document SARIF 2.1.0 pour les outils d’analyse de code (servi en application/json).

Paramètres

NomOùTypeObligatoireDescription
idcheminstringouiIdentifiant du domaine, ou son nom (example.com).
formatrequêtesarifnonsarif : répondre en SARIF 2.1.0.

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains/example.com/latest?format=sarif"

Réponse 200

L’audit (JSON), ou du SARIF (application/sarif+json) avec format=sarif.

{
  "apiVersion": 1,
  "domain": "example.com",
  "audit": {
    "id": "8b1d5e2f-7c3a-4f6e-b2d9-0a4c6e8f1a23",
    "domain_id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
    "started_at": "2026-11-12T03:14:07.000Z",
    "finished_at": "2026-11-12T03:16:52.000Z",
    "duration_ms": 165000,
    "scanner_version": "0.13.1",
    "score": 72,
    "grade": "C",
    "counts": {
      "high": 2,
      "medium": 3,
      "low": 4,
      "info": 6
    },
    "snapshot": {},
    "errors": [],
    "diff": null,
    "attention": true,
    "trigger": "schedule",
    "created_at": "2026-11-12T03:16:52.000Z"
  },
  "findings": [
    {
      "fp": "5d0e3b7c1a9f4e26",
      "id": "email.dmarc.p_none",
      "severity": "high",
      "category": "email",
      "target": "example.com",
      "title": "DMARC policy only monitors (p=none)",
      "fix": "Move the policy to quarantine, then reject."
    }
  ],
  "accepted": []
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
domainstringName of the domain.
auditobjectThe audit as stored (its raw facts left out). Kept as it always was for existing scripts; further fields may appear.
audit.idstring (uuid)
audit.domain_idstring (uuid)
audit.started_atstring (date-time)
audit.finished_at?string (date-time) | null
audit.duration_ms?integer | null
audit.scanner_versionstring
audit.scoreinteger
audit.gradestring
audit.counts{ severity: integer }Open findings by severity (a severity with none may be left out).
audit.errors?object[]Checks that could not run.
audit.diff?object | nullChanges since the previous audit.
audit.attentionboolean
audit.triggerstring
audit.snapshot?objectSummary kept for comparisons.
audit.created_at?string (date-time)
findingsobject[]Open findings, worst first, in English.
findings[].fp?stringFingerprint.
findings[].idstringId of the check.
findings[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
findings[].categorystring
findings[].targetstring
findings[].titlestring
findings[].detail?string
findings[].evidence?string
findings[].fix?string
findings[].references?string[]
findings[].acceptedReason?string
findings[].acceptedUntil?string | null
acceptedobject[]Accepted findings.
accepted[].fp?stringFingerprint.
accepted[].idstringId of the check.
accepted[].severitycritical | high | medium | low | infoFrom the worst to the mildest.
accepted[].categorystring
accepted[].targetstring
accepted[].titlestring
accepted[].detail?string
accepted[].evidence?string
accepted[].fix?string
accepted[].references?string[]
accepted[].acceptedReason?string
accepted[].acceptedUntil?string | null

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 404 Absent de cet espace (not_found), ou le domaine n’a pas encore d’audit (no_audit).
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

POST /api/v1/domains/{id}/audit

Lancer un audit

Lance un audit d’un domaine prouvé ; la clé doit avoir le droit audit. Répond 202 avec une exécution à suivre à /api/v1/runs/{id}. Si un audit du domaine attend déjà ou tourne, la nouvelle exécution suit celui-là (alreadyQueued). Même limite que le bouton Auditer maintenant (un par heure et par domaine avec Solo) et, par l’API, un toutes les 15 minutes par domaine. Pas de corps de requête.

Paramètres

NomOùTypeObligatoireDescription
idcheminstringouiIdentifiant du domaine, ou son nom (example.com).

Exemple

curl -X POST -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/domains/example.com/audit"

Réponse 202

Accepté : l’audit est en file d’attente (ou l’était déjà).

{
  "apiVersion": 1,
  "queued": true,
  "alreadyQueued": false,
  "run": {
    "id": "6f1c9d2e-4b7a-4c3e-8f5d-1a2b3c4d5e6f",
    "status": "queued",
    "domain": {
      "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
      "name": "example.com"
    },
    "requestedAt": "2026-11-12T09:00:02.000Z",
    "progress": null,
    "audit": null,
    "previous": {
      "id": "c4e7a1b9-2d5f-4a8c-9e3b-6f1a2d4c8e57",
      "startedAt": "2026-11-05T03:14:07.000Z",
      "score": 72,
      "grade": "C"
    },
    "scoreDelta": null,
    "links": {
      "self": "/api/v1/runs/6f1c9d2e-4b7a-4c3e-8f5d-1a2b3c4d5e6f",
      "app": "https://app.perimetry.cloud/domains/3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10"
    }
  }
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
queuedtrue
alreadyQueuedbooleanTrue when an audit of the domain was already waiting or running: this run follows it.
runobject
run.idstring (uuid)Id of the run, to follow at /api/v1/runs/{id}.
run.statusqueued | running | done | failedqueued: waiting for a scanner; running: under way (see progress); done: the audit is in; failed: the audit did not finish.
run.domainobject
run.domain.idstring (uuid)Id of the domain.
run.domain.namestringName of the domain.
run.requestedAtstring (date-time)When the audit was asked for.
run.progressobject | nullWhile running only.
run.progress.percentnumber | nullPercentage done.
run.progress.stepstring | nullCurrent step.
run.error?stringFailed runs only: what happened.
run.auditobject | nullThe audit, once done.
run.audit.idstring (uuid)
run.audit.startedAtstring (date-time)
run.audit.finishedAtstring (date-time) | null
run.audit.triggerstringmanual for an audit started on demand.
run.audit.scoreinteger
run.audit.gradestring
run.audit.counts{ severity: integer }Open findings by severity (a severity with none may be left out).
run.audit.newFindingsinteger | nullFindings that were not in the previous audit.
run.audit.fixedFindingsinteger | nullFindings of the previous audit that are gone.
run.audit.worstAddedstring | nullWorst severity among the new findings.
run.previousobject | nullThe audit before this one.
run.previous.idstring (uuid)Id of the audit.
run.previous.startedAtstring (date-time) | nullWhen the audit started.
run.previous.scoreintegerScore out of 100.
run.previous.gradestringGrade, A to F.
run.scoreDeltainteger | nullChange of score since the previous audit.
run.linksobject
run.links.selfstringThis run in the API.
run.links.appstring (uri)The domain’s page in the application.

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre ne comprend pas l’API (plan_excludes_api), ou la clé ne peut pas lancer d’audit (insufficient_scope).
  • 404 Absent de cet espace (code not_found).
  • 409 Le domaine n’est pas prouvé (code not_verified).
  • 429 La limite horaire est atteinte (rate_limited), trop de requêtes sont venues avec une mauvaise clé (too_many_failures), ou un audit à la demande de ce domaine a eu lieu trop récemment (audit_too_soon). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).
  • 503 L’audit n’a pas pu être mis en file ; réessayez (code unavailable).
  • 507 L’espace a utilisé le stockage de son offre (storage_quota), ou notre stockage est plein pour le moment (storage_full) : pas d’audit à la demande tant qu’il n’y a pas de place. Supprimez d’anciennes données ou passez à une offre plus grande. Tout ce qui est enregistré reste lisible.

GET /api/v1/runs/{id}

Où en est un audit lancé par l’API

queued (en attente), running (en cours, avec sa progression), done (terminé : nouvelle note, note précédente, évolution) ou failed (échoué). Interrogez toutes les 10 à 30 secondes ; un audit prend en général quelques minutes.

Paramètres

NomOùTypeObligatoireDescription
idcheminstring (uuid)ouiIdentifiant de l’exécution, donné par POST /api/v1/domains/{id}/audit.

Exemple

curl -H "Authorization: Bearer $PERIMETRY_API_KEY" \
  "https://app.perimetry.cloud/api/v1/runs/6f1c9d2e-4b7a-4c3e-8f5d-1a2b3c4d5e6f"

Réponse 200

{
  "apiVersion": 1,
  "run": {
    "id": "6f1c9d2e-4b7a-4c3e-8f5d-1a2b3c4d5e6f",
    "status": "done",
    "domain": {
      "id": "3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10",
      "name": "example.com"
    },
    "requestedAt": "2026-11-12T09:00:02.000Z",
    "progress": null,
    "audit": {
      "id": "8b1d5e2f-7c3a-4f6e-b2d9-0a4c6e8f1a23",
      "startedAt": "2026-11-12T09:00:05.000Z",
      "finishedAt": "2026-11-12T09:02:41.000Z",
      "trigger": "manual",
      "score": 78,
      "grade": "C",
      "counts": {
        "critical": 0,
        "high": 1,
        "medium": 3,
        "low": 4,
        "info": 6
      },
      "newFindings": 0,
      "fixedFindings": 1,
      "worstAdded": null
    },
    "previous": {
      "id": "c4e7a1b9-2d5f-4a8c-9e3b-6f1a2d4c8e57",
      "startedAt": "2026-11-05T03:14:07.000Z",
      "score": 72,
      "grade": "C"
    },
    "scoreDelta": 6,
    "links": {
      "self": "/api/v1/runs/6f1c9d2e-4b7a-4c3e-8f5d-1a2b3c4d5e6f",
      "app": "https://app.perimetry.cloud/domains/3f9c2a64-5b1e-4c0a-9a57-2f6d8e1b4c10"
    }
  }
}
Champs de la réponse

Descriptions des champs en anglais, comme dans la spécification.

NomTypeDescription
apiVersion1
runobject
run.idstring (uuid)Id of the run, to follow at /api/v1/runs/{id}.
run.statusqueued | running | done | failedqueued: waiting for a scanner; running: under way (see progress); done: the audit is in; failed: the audit did not finish.
run.domainobject
run.domain.idstring (uuid)Id of the domain.
run.domain.namestringName of the domain.
run.requestedAtstring (date-time)When the audit was asked for.
run.progressobject | nullWhile running only.
run.progress.percentnumber | nullPercentage done.
run.progress.stepstring | nullCurrent step.
run.error?stringFailed runs only: what happened.
run.auditobject | nullThe audit, once done.
run.audit.idstring (uuid)
run.audit.startedAtstring (date-time)
run.audit.finishedAtstring (date-time) | null
run.audit.triggerstringmanual for an audit started on demand.
run.audit.scoreinteger
run.audit.gradestring
run.audit.counts{ severity: integer }Open findings by severity (a severity with none may be left out).
run.audit.newFindingsinteger | nullFindings that were not in the previous audit.
run.audit.fixedFindingsinteger | nullFindings of the previous audit that are gone.
run.audit.worstAddedstring | nullWorst severity among the new findings.
run.previousobject | nullThe audit before this one.
run.previous.idstring (uuid)Id of the audit.
run.previous.startedAtstring (date-time) | nullWhen the audit started.
run.previous.scoreintegerScore out of 100.
run.previous.gradestringGrade, A to F.
run.scoreDeltainteger | nullChange of score since the previous audit.
run.linksobject
run.links.selfstringThis run in the API.
run.links.appstring (uri)The domain’s page in the application.

Erreurs

  • 401 La clé manque, est mal formée, révoquée ou inconnue (code unauthorized).
  • 403 L’offre de l’espace ne comprend pas l’API (code plan_excludes_api). X-RateLimit-Limit et X-RateLimit-Remaining valent 0.
  • 404 Absent de cet espace (code not_found).
  • 429 La limite horaire est atteinte (rate_limited), ou trop de requêtes sont venues avec une mauvaise clé (too_many_failures). Attendez Retry-After secondes.
  • 500 Un problème de notre côté (code internal).

Spécification

Ce document.

GET /api/v1/openapi.json

Cette spécification

La description OpenAPI 3.1 de l’API. Publique : sans clé, non décomptée de la limite horaire.

Sans clé.

Exemple

curl \
  "https://app.perimetry.cloud/api/v1/openapi.json"

Réponse 200

Le document OpenAPI.