REST API Domains - edmozley/freeitsm GitHub Wiki
๐ REST API: Domains
The usage guide for the Domains module of the REST API: the domain register, registry lookups, the security checks and their findings, and each domain's history. Mirrors the interactive documentation at System โ API โ Documentation.
[!NOTE] New to the API? The basics table on REST API: Tickets covers base URL, authentication, the response envelope, error codes, rate limits and PATCH semantics - identical across all modules.
What's distinctive about Domains:
| ๐ข Company-scoped | Lists are filtered to the key's companies; a domain outside them is a 404 on every by-id route. New domains land in company_id (checked against the key) or the key's default company. |
| ๐ Auth codes never cross the API | Responses carry only auth_code_set; a body containing auth_code is refused with 422. A transfer secret belongs behind the analyst permission and its audit trail. |
| ๐ Lookups and checks on demand | POST /domains/{id}/lookup asks the registry now; POST /domains/{id}/check re-grades now. Both return the domain with what changed. |
| ๐ Findings in English | GET /domains/{id}/findings returns each finding's title and advice filled in, for a consumer with no language file. |
Give a key the Domains permissions (read / create / update / delete, and internal for internal monitoring) under System โ API.
๐ Quick start
B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_โฆ"
# Everything expiring in the next 60 days, soonest first
curl -s -H "$K" "$B/domains?expiring_within_days=60&sort=expiry_date" | jq '.data[] | {domain_name, expiry_date, days_remaining}'
# Add a domain and fill in the registry details straight away
curl -s -H "$K" -H "Content-Type: application/json" -X POST \
-d '{"domain_name":"example.com","purpose":"primary","lookup":true}' "$B/domains"
# What is wrong with it, and how to fix it
curl -s -H "$K" "$B/domains/42/findings" | jq '.data[] | select(.level=="fail" or .level=="warn") | {title, advice}'
Endpoints
| Method | Path | Permission | What |
|---|---|---|---|
| GET | /domains |
domains.read | List / search |
| POST | /domains |
domains.create | Add a domain |
| GET | /domains/{id} |
domains.read | One domain |
| PATCH | /domains/{id} |
domains.update | Change fields |
| DELETE | /domains/{id} |
domains.delete | Delete (with its history) |
| POST | /domains/{id}/lookup |
domains.update | Refresh from the registry |
| POST | /domains/{id}/check |
domains.update | Run the checks now |
| GET | /domains/{id}/findings |
domains.read | The last check's findings |
| GET | /domains/{id}/history |
domains.read | Every change, newest first |
| GET | /domain-statuses |
domains.read | Statuses (alerts_enabled marks the ones that stop reminders) |
| GET | /domain-registrar-accounts |
domains.read | Registrar accounts (company-scoped; no passwords exist to return) |
| GET | /domains/{id}/endpoints |
domains.read | An internal domain's HTTPS endpoints and their last results |
| POST | /domains/{id}/endpoints |
domains.internal | Add an endpoint |
| PATCH | /domains/{id}/endpoints/{endpoint_id} |
domains.internal | Change an endpoint (partial) |
| DELETE | /domains/{id}/endpoints/{endpoint_id} |
domains.internal | Remove an endpoint |
| GET | /domains/{id}/expected-records |
domains.read | An internal domain's expected DNS records |
| POST | /domains/{id}/expected-records |
domains.internal | Add an expected record |
| PATCH | /domains/{id}/expected-records/{record_id} |
domains.internal | Change an expected record (partial) |
| DELETE | /domains/{id}/expected-records/{record_id} |
domains.internal | Remove an expected record |
| GET | /domain-trust-profiles |
domains.internal | Trust profiles, with each CA's name and expiry (never the certificates) |
| POST | /domain-trust-profiles |
domains.internal | Add a trust profile |
| PATCH | /domain-trust-profiles/{id} |
domains.internal | Rename it, or replace its certificates |
| DELETE | /domain-trust-profiles/{id} |
domains.internal | Delete it (refused while an endpoint uses it) |
The last twelve are internal monitoring; every domains.internal route also needs the key's analyst to hold the Internal monitoring permission.
List filters
q, domain_name (tidied like input, so a URL matches - and with split DNS it can return two records, one per scope), scope (public or internal), status_id, owner_analyst_id, registrar_supplier_id, registrar_account_id, purpose, tag, expiring_within_days, expired=true, transfer_lock=true|false, grade=D,F, company_id, sort (expiry_date default, domain_name, security_score, ssl_expiry_date, created_at, id; prefix - for descending), page / per_page.
Writes
Only domain_name is required on create. A name may exist once as "scope": "public" and once as "scope": "internal" in a company (split DNS); a second record of the same name and scope is a 409 conflict, as is changing a record's scope while its twin holds that scope. Lookups (status_id, owner_analyst_id, registrar_supplier_id, registrar_account_id, tech_contact_id, tech_analyst_id, contract_id, customer_user_id, customer_supplier_id, customer_contact_id) are validated with a 422 naming the field. customer_user_id is the domain's customer โ a user in the domain's own company โ and comes back as customer: {id, name, email}.
Customer and technical contact (3.1.0, #162). A domain has one customer โ customer_user_id, or customer_supplier_id with an optional customer_contact_id โ and one technical contact โ tech_contact_id (a supplier contact) or tech_analyst_id. Setting one kind clears the other, so a PATCH need only name the new one; naming both kinds in one request is a 422. A customer_contact_id on its own sets customer_supplier_id to that contact's supplier; a contact from a different supplier than the one you name is a 422. Clearing customer_supplier_id clears its contact too. The responses keep customer and tech_contact exactly as before and add tech_analyst: {id, name}, customer_supplier: {id, name} and customer_contact: {id, name, email} beside them, each null when not set. Before Database Verification has added the columns, the three new fields are accepted and ignored. Dates are YYYY-MM-DD. purpose is one of primary, secondary, redirect, email_only, defensive, parked, campaign; renewal_mode one of auto, manual, do_not_renew, unknown. On PATCH, omit a field to leave it, send "" or null to clear it.
Every write goes through the same service as the screens: the same validation, a history row per changed field (source api), and the domain.created / domain.updated / domain.deleted workflow events.
Cost centre (3.9.0, #188). Send cost_centre_id - a cost centre of the domain's own company - or cost_centre with its code ("0010"), which is looked up in that company and refused with a 422 if it does not exist. Send "" or null to clear it. Responses carry cost_centre_id and cost_centre_label ("0010 Finance"), plus cost_centre: the free text recorded before 3.9.0, kept until a real cost centre is chosen. See Cost centres.
Errors
| HTTP | code | When |
|---|---|---|
| 400 | invalid_parameter | unknown sort or purpose |
| 403 | forbidden | company_id outside the key's scope |
| 404 | not_found | no such domain, or outside the key's companies |
| 409 | conflict | the name is already in the register for that company |
| 422 | invalid_field / missing_field | bad value, unknown id, empty PATCH - or auth_code in the body |
| 502 | lookup_failed | the registry did not answer, rate-limited, or has no such registration |
Internal monitoring (4.9.0)
For an internal domain (Domains - Internal domains): the HTTPS endpoints whose certificates (and, optionally, one page) are checked, the DNS records each name is expected to hold, and the trust profiles - your own certificate authorities - that endpoints are checked against.
Who may do what. Reading a domain's endpoints and expected records needs only domains.read: everybody who can see the domain sees the results. Everything else needs domains.internal and the analyst the key acts as must hold the Internal monitoring permission (System โ Roles) - endpoints decide which internal addresses the server connects to, so a key can never do here what its analyst could not do on screen. Trust profiles serve every company, so on a multi-company install only a key with no company limit may change them.
Endpoints. Body fields: host (the domain or a name under it - a name, never an address), port (default 443; checked against the permitted ports when the check runs), warn_days (1-120, default 21), trust_profile_id (null for the server's normal CA bundle), and the optional HTTP check: http_path (starts with /, no spaces) and http_expect (status codes such as "200, 204", 200 when empty; stored tidied). The page is asked only after the certificate passes, only its status is read, and a redirect is never followed - list a 3xx if that is the right answer. Responses carry status (healthy, expiring, problem or null - the last completed result), not_checked_since (the latest attempt could not run), detail (why, e.g. untrusted, http_status, network_refused), address, certificate and the last_attempt_at / last_completed_at / last_healthy_at dates. Changing the host, port, trust profile or HTTP check clears the old results: they described a different target.
Expected records. host, record_type (A, AAAA, CNAME, NS, SOA) and expected_values - a list (text one per line is accepted too). An empty list means the record only has to exist. Values are stored normalised and sorted; an invalid value is a 422 naming it; CNAME and SOA take one value. The result of each is a finding in GET /domains/{id}/findings (int_expected_ok, _missing, _mismatch, _not_checked).
Trust profiles. name and ca_pem (one or more -----BEGIN CERTIFICATE----- blocks; a private key is refused). Responses return summary (each CA's name and expiry) and used_by, never the certificates. A PATCH without ca_pem renames the profile and keeps its certificates.
PATCH is partial on all three: omitted fields keep their stored values, then the same rules as the screen run. Send null to clear http_path.
| HTTP | code | When |
|---|---|---|
| 403 | forbidden | no domains.internal; the key's analyst lacks Internal monitoring; a company-limited key changing a trust profile |
| 404 | not_found | no such domain (or outside the key's companies), endpoint, expected record or trust profile |
| 422 | not_ready | Database Verification has not been run since upgrading |
| 422 | not_internal | an endpoint or expected record on a public domain |
| 422 | duplicate | the same host and port, or the same name and type, twice |
| 422 | in_use | deleting a trust profile an endpoint uses |
| 422 | the field's name | a value the screen would refuse, with the same message |
How it is built: Internal Domain Monitoring - Developer Guide.
See also: Domains ยท Domains developer guide ยท REST API