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.
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/meLes 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 :
| Offre | Appels par heure |
|---|---|
| Solo | 300 |
| Agency | 1 200 |
| Agency Plus | 3 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"}| Statut | Code | Signification |
|---|---|---|
| 400 | invalid_parameter | Un paramètre a une valeur que l’API n’accepte pas ; le message indique les valeurs possibles. |
| 401 | unauthorized | La clé manque, est mal formée, révoquée ou inconnue. |
| 403 | plan_excludes_api | L’offre de l’espace ne comprend pas l’API. |
| 403 | insufficient_scope | La clé ne peut pas lancer d’audit : créez-en une avec le droit audit. |
| 404 | not_found | Absent de cet espace (un domaine d’un autre espace répond de même). |
| 404 | no_audit | Le domaine n’a pas encore d’audit. |
| 409 | not_verified | Un audit a été demandé pour un domaine non prouvé. |
| 429 | rate_limited | La limite horaire est atteinte : attendez Retry-After secondes. |
| 429 | too_many_failures | Trop de requêtes avec une mauvaise clé depuis votre réseau : quinze minutes d’attente. |
| 429 | audit_too_soon | Un audit à la demande de ce domaine a eu lieu trop récemment : attendez Retry-After secondes. |
| 500 | internal | Un problème de notre côté. |
| 503 | unavailable | L’audit n’a pas pu être mis en file : réessayez. |
| 507 | storage_quota | L’espace a utilisé le stockage de son offre : supprimez d’anciennes données ou passez à une offre plus grande. |
| 507 | storage_full | Notre 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
workspace | object | |
workspace.name | string | null | Name of the workspace. |
workspace.plan | string | Plan of the workspace: solo, agency, agency-plus. |
key | object | |
key.name | string | Name given to the key. |
key.prefix | string | First characters of the key. |
key.scopes | read | audit[] | read: read results; audit: also start audits. |
rateLimit | object | |
rateLimit.limit | integer | Calls allowed per clock hour, for the whole workspace. |
rateLimit.remaining | integer | Calls left this hour (this one counted). |
rateLimit.resetAt | string (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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
name | requête | string | non | Ne 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
domains | object[] | |
domains[].id | string (uuid) | Id of the domain. |
domains[].name | string | Name of the domain, such as example.com. |
domains[].status | pending | verified | lost | paused | pending: ownership not proved yet; verified: proved and audited; lost: the proof disappeared; paused: audits stopped. |
domains[].verified | boolean | True when the status is verified. |
domains[].client | string | null | Client the domain belongs to (agency plans), or null. |
domains[].score | integer | null | Score of the last audit, out of 100; null before the first audit. |
domains[].grade | string | null | Grade of the last audit, A to F. |
domains[].counts | { severity: integer } | null | Open findings of the last audit by severity. |
domains[].lastAuditAt | string (date-time) | null | When the last audit started. |
domains[].nextAuditAt | string (date-time) | null | When the next scheduled audit is due. |
domains[].attention | boolean | null | True when the last audit found something new and serious. |
domains[].links | object | |
domains[].links.app | string (uri) | The domain’s page in the application. |
domains[].links.api | string | This 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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string | oui | Identifiant du domaine, ou son nom (example.com). |
lang | requête | en | fr | de | non | Langue 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
domain | object | |
domain.id | string (uuid) | Id of the domain. |
domain.name | string | Name of the domain, such as example.com. |
domain.status | pending | verified | lost | paused | pending: ownership not proved yet; verified: proved and audited; lost: the proof disappeared; paused: audits stopped. |
domain.verified | boolean | True when the status is verified. |
domain.client | string | null | Client the domain belongs to (agency plans), or null. |
domain.score | integer | null | Score of the last audit, out of 100; null before the first audit. |
domain.grade | string | null | Grade of the last audit, A to F. |
domain.counts | { severity: integer } | null | Open findings of the last audit by severity. |
domain.lastAuditAt | string (date-time) | null | When the last audit started. |
domain.nextAuditAt | string (date-time) | null | When the next scheduled audit is due. |
domain.attention | boolean | null | True when the last audit found something new and serious. |
domain.links | object | |
domain.links.app | string (uri) | The domain’s page in the application. |
domain.links.api | string | This domain in the API. |
lastAudit | object | null | Null before the first audit. |
lastAudit.id | string (uuid) | |
lastAudit.startedAt | string (date-time) | |
lastAudit.finishedAt | string (date-time) | null | |
lastAudit.trigger | string | schedule or manual. |
lastAudit.scannerVersion | string | Version of the scanner. |
lastAudit.score | integer | |
lastAudit.grade | string | |
lastAudit.counts | { severity: integer } | Open findings by severity (a severity with none may be left out). |
lastAudit.attention | boolean | Something new and serious. |
lastAudit.previous | object | null | |
lastAudit.previous.id | string (uuid) | Id of the audit. |
lastAudit.previous.startedAt | string (date-time) | null | When the audit started. |
lastAudit.previous.score | integer | Score out of 100. |
lastAudit.previous.grade | string | Grade, A to F. |
lastAudit.scoreDelta | integer | null | Change of score since the previous audit. |
lastAudit.newFindings | integer | null | Findings that were not in the previous audit. |
lastAudit.fixedFindings | integer | null | Findings of the previous audit that are gone. |
doFirst | object[] | The (at most) three findings to fix first. |
doFirst[].fp | string | null | Fingerprint: the same check on the same target keeps the same fingerprint from one audit to the next. |
doFirst[].id | string | Id of the check, such as email.dmarc.missing. |
doFirst[].severity | critical | high | medium | low | info | From the worst to the mildest. |
doFirst[].title | string | Title, in the language asked for. |
doFirst[].target | string | Host, 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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string | oui | Identifiant du domaine, ou son nom (example.com). |
min_severity | requête | critical | high | medium | low | info | non | Garder 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_accepted | requête | 1 | true | non | 1 ou true : renvoyer aussi les constats acceptés, dans accepted. |
limit | requête | integer | non | Au plus ce nombre de constats (et autant de constats acceptés). (par défaut 500) |
lang | requête | en | fr | de | non | Langue 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
domain | object | |
domain.id | string (uuid) | Id of the domain. |
domain.name | string | Name of the domain. |
audit | object | |
audit.id | string (uuid) | |
audit.startedAt | string (date-time) | |
audit.score | integer | |
audit.grade | string | |
audit.counts | { severity: integer } | Open findings by severity (a severity with none may be left out). |
total | integer | Open findings at or above min_severity, before limit. |
findings | object[] | Open findings, worst first. |
findings[].fp | string | null | Fingerprint, stable from one audit to the next. |
findings[].id | string | Id of the check. |
findings[].severity | critical | high | medium | low | info | From the worst to the mildest. |
findings[].category | string | email, web, dns, exposure… |
findings[].target | string | Host, address or record the finding is about. |
findings[].title | string | Title, in the language asked for. |
findings[].detail | string | null | Why it matters. |
findings[].evidence | string | null | What the audit saw. |
findings[].fix | string | null | How to fix it. |
findings[].references | string (uri)[] | Standards and documentation. |
findings[].records | object[] | DNS records ready to copy that fix it, when the audit allows (always empty for an accepted finding). |
findings[].records[].type | string | TXT, CAA, MX… or "file" for a file to serve (MTA-STS policy). |
findings[].records[].name | string | Name of the record (or address of the file). |
findings[].records[].value | string | Value to publish. |
findings[].records[].replaces? | string | null | The current value this one replaces, when there is one. |
findings[].records[].note? | string | What to check before publishing. |
findings[].compliance | string[] | NIS2, Cyber Essentials and ISO 27001 requirements the finding relates to. |
findings[].acceptedReason? | string | null | Accepted findings only: why the risk was accepted. |
findings[].acceptedUntil? | string | null | Accepted 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[].fp | string | null | Fingerprint, stable from one audit to the next. |
accepted[].id | string | Id of the check. |
accepted[].severity | critical | high | medium | low | info | From the worst to the mildest. |
accepted[].category | string | email, web, dns, exposure… |
accepted[].target | string | Host, address or record the finding is about. |
accepted[].title | string | Title, in the language asked for. |
accepted[].detail | string | null | Why it matters. |
accepted[].evidence | string | null | What the audit saw. |
accepted[].fix | string | null | How to fix it. |
accepted[].references | string (uri)[] | Standards and documentation. |
accepted[].records | object[] | DNS records ready to copy that fix it, when the audit allows (always empty for an accepted finding). |
accepted[].records[].type | string | TXT, CAA, MX… or "file" for a file to serve (MTA-STS policy). |
accepted[].records[].name | string | Name of the record (or address of the file). |
accepted[].records[].value | string | Value to publish. |
accepted[].records[].replaces? | string | null | The current value this one replaces, when there is one. |
accepted[].records[].note? | string | What to check before publishing. |
accepted[].compliance | string[] | NIS2, Cyber Essentials and ISO 27001 requirements the finding relates to. |
accepted[].acceptedReason? | string | null | Accepted findings only: why the risk was accepted. |
accepted[].acceptedUntil? | string | null | Accepted 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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string | oui | Identifiant du domaine, ou son nom (example.com). |
lang | requête | en | fr | de | non | Langue 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
domain | object | |
domain.id | string (uuid) | Id of the domain. |
domain.name | string | Name of the domain. |
audit | object | |
audit.id | string (uuid) | |
audit.startedAt | string (date-time) | |
audit.score | integer | |
audit.grade | string | |
previous | object | null | Null at the first audit. |
previous.id | string (uuid) | null | |
previous.startedAt | string (date-time) | null | |
previous.score | integer | |
previous.grade | string | null | |
comparison | object | null | Null at the first audit. |
comparison.score | object | |
comparison.score.from | integer | |
comparison.score.to | integer | |
comparison.score.delta | integer | |
comparison.attention | boolean | Something new and serious. |
comparison.worstAdded | string | null | Worst severity among the new findings. |
comparison.added | object[] | |
comparison.added[].fp | string | null | Fingerprint: the same check on the same target keeps the same fingerprint from one audit to the next. |
comparison.added[].id | string | Id of the check, such as email.dmarc.missing. |
comparison.added[].severity | critical | high | medium | low | info | From the worst to the mildest. |
comparison.added[].title | string | Title, in the language asked for. |
comparison.added[].target | string | Host, address or record the finding is about. |
comparison.resolved | object[] | |
comparison.resolved[].fp | string | null | Fingerprint: the same check on the same target keeps the same fingerprint from one audit to the next. |
comparison.resolved[].id | string | Id of the check, such as email.dmarc.missing. |
comparison.resolved[].severity | critical | high | medium | low | info | From the worst to the mildest. |
comparison.resolved[].title | string | Title, in the language asked for. |
comparison.resolved[].target | string | Host, address or record the finding is about. |
comparison.severityChanged | object[] | |
comparison.severityChanged[].fp | string | null | Fingerprint: the same check on the same target keeps the same fingerprint from one audit to the next. |
comparison.severityChanged[].id | string | Id of the check, such as email.dmarc.missing. |
comparison.severityChanged[].severity | critical | high | medium | low | info | From the worst to the mildest. |
comparison.severityChanged[].title | string | Title, in the language asked for. |
comparison.severityChanged[].target | string | Host, address or record the finding is about. |
comparison.severityChanged[].from | string | Severity before. |
comparison.severityChanged[].to | string | Severity now. |
comparison.infra | object | |
comparison.infra.newHosts | string[] | |
comparison.infra.goneHosts | string[] | |
comparison.infra.newLookalikes | string[] | |
comparison.infra.newCloud | string[] | |
comparison.infra.ports | object[] | |
comparison.infra.ports[].ip | string | |
comparison.infra.ports[].opened | integer[] | |
comparison.infra.ports[].closed | integer[] | |
comparison.infra.certificates | object[] | Certificates that changed. |
comparison.infra.certificates[].host | string | |
comparison.infra.certificates[].from | string | |
comparison.infra.certificates[].to | string | |
comparison.infra.newCertificates | object[] | New certificates seen in the transparency logs. |
comparison.infra.newCertificates[].issuer | string | |
comparison.infra.newCertificates[].names | string[] | |
comparison.infra.newCertificates[].notBefore | string |
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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string | oui | Identifiant du domaine, ou son nom (example.com). |
limit | requête | integer | non | Au 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
domain | object | |
domain.id | string (uuid) | Id of the domain. |
domain.name | string | Name of the domain. |
audits | object[] | Newest first. |
audits[].id | string (uuid) | |
audits[].startedAt | string (date-time) | |
audits[].trigger | string | |
audits[].score | integer | |
audits[].grade | string | |
audits[].counts | { severity: integer } | Open findings by severity (a severity with none may be left out). |
audits[].attention | boolean |
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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string | oui | Identifiant du domaine, ou son nom (example.com). |
format | requête | sarif | non | sarif : 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
domain | string | Name of the domain. |
audit | object | The audit as stored (its raw facts left out). Kept as it always was for existing scripts; further fields may appear. |
audit.id | string (uuid) | |
audit.domain_id | string (uuid) | |
audit.started_at | string (date-time) | |
audit.finished_at? | string (date-time) | null | |
audit.duration_ms? | integer | null | |
audit.scanner_version | string | |
audit.score | integer | |
audit.grade | string | |
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 | null | Changes since the previous audit. |
audit.attention | boolean | |
audit.trigger | string | |
audit.snapshot? | object | Summary kept for comparisons. |
audit.created_at? | string (date-time) | |
findings | object[] | Open findings, worst first, in English. |
findings[].fp? | string | Fingerprint. |
findings[].id | string | Id of the check. |
findings[].severity | critical | high | medium | low | info | From the worst to the mildest. |
findings[].category | string | |
findings[].target | string | |
findings[].title | string | |
findings[].detail? | string | |
findings[].evidence? | string | |
findings[].fix? | string | |
findings[].references? | string[] | |
findings[].acceptedReason? | string | |
findings[].acceptedUntil? | string | null | |
accepted | object[] | Accepted findings. |
accepted[].fp? | string | Fingerprint. |
accepted[].id | string | Id of the check. |
accepted[].severity | critical | high | medium | low | info | From the worst to the mildest. |
accepted[].category | string | |
accepted[].target | string | |
accepted[].title | string | |
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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string | oui | Identifiant 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
queued | true | |
alreadyQueued | boolean | True when an audit of the domain was already waiting or running: this run follows it. |
run | object | |
run.id | string (uuid) | Id of the run, to follow at /api/v1/runs/{id}. |
run.status | queued | running | done | failed | queued: waiting for a scanner; running: under way (see progress); done: the audit is in; failed: the audit did not finish. |
run.domain | object | |
run.domain.id | string (uuid) | Id of the domain. |
run.domain.name | string | Name of the domain. |
run.requestedAt | string (date-time) | When the audit was asked for. |
run.progress | object | null | While running only. |
run.progress.percent | number | null | Percentage done. |
run.progress.step | string | null | Current step. |
run.error? | string | Failed runs only: what happened. |
run.audit | object | null | The audit, once done. |
run.audit.id | string (uuid) | |
run.audit.startedAt | string (date-time) | |
run.audit.finishedAt | string (date-time) | null | |
run.audit.trigger | string | manual for an audit started on demand. |
run.audit.score | integer | |
run.audit.grade | string | |
run.audit.counts | { severity: integer } | Open findings by severity (a severity with none may be left out). |
run.audit.newFindings | integer | null | Findings that were not in the previous audit. |
run.audit.fixedFindings | integer | null | Findings of the previous audit that are gone. |
run.audit.worstAdded | string | null | Worst severity among the new findings. |
run.previous | object | null | The audit before this one. |
run.previous.id | string (uuid) | Id of the audit. |
run.previous.startedAt | string (date-time) | null | When the audit started. |
run.previous.score | integer | Score out of 100. |
run.previous.grade | string | Grade, A to F. |
run.scoreDelta | integer | null | Change of score since the previous audit. |
run.links | object | |
run.links.self | string | This run in the API. |
run.links.app | string (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
| Nom | Où | Type | Obligatoire | Description |
|---|---|---|---|---|
id | chemin | string (uuid) | oui | Identifiant 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.
| Nom | Type | Description |
|---|---|---|
apiVersion | 1 | |
run | object | |
run.id | string (uuid) | Id of the run, to follow at /api/v1/runs/{id}. |
run.status | queued | running | done | failed | queued: waiting for a scanner; running: under way (see progress); done: the audit is in; failed: the audit did not finish. |
run.domain | object | |
run.domain.id | string (uuid) | Id of the domain. |
run.domain.name | string | Name of the domain. |
run.requestedAt | string (date-time) | When the audit was asked for. |
run.progress | object | null | While running only. |
run.progress.percent | number | null | Percentage done. |
run.progress.step | string | null | Current step. |
run.error? | string | Failed runs only: what happened. |
run.audit | object | null | The audit, once done. |
run.audit.id | string (uuid) | |
run.audit.startedAt | string (date-time) | |
run.audit.finishedAt | string (date-time) | null | |
run.audit.trigger | string | manual for an audit started on demand. |
run.audit.score | integer | |
run.audit.grade | string | |
run.audit.counts | { severity: integer } | Open findings by severity (a severity with none may be left out). |
run.audit.newFindings | integer | null | Findings that were not in the previous audit. |
run.audit.fixedFindings | integer | null | Findings of the previous audit that are gone. |
run.audit.worstAdded | string | null | Worst severity among the new findings. |
run.previous | object | null | The audit before this one. |
run.previous.id | string (uuid) | Id of the audit. |
run.previous.startedAt | string (date-time) | null | When the audit started. |
run.previous.score | integer | Score out of 100. |
run.previous.grade | string | Grade, A to F. |
run.scoreDelta | integer | null | Change of score since the previous audit. |
run.links | object | |
run.links.self | string | This run in the API. |
run.links.app | string (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.