nsindocs Search the docs⌘K
  1. Docs
  2. API reference
  3. DNS Records

Start a record import session

posthttps://api.nsin.ir/domains/{domain}/records/import-sessions

Auth: Authorization: Bearer nsin_… or X-Api-Key: nsin_… — see API keys.

Scans the domain's current nameservers — a zone transfer where they allow one, otherwise a sweep of common names — into a staging session. Nothing is written to the zone. Requires records.edit.

The scan runs in the background: the session comes back scanning with an empty records list, and you poll GET .../records/import-sessions/current until it turns ready.

Each staged row carries the proxy setting it would be imported with (proxied): on for the website rows the edge can carry — a proxyable type pointing at an address the edge accepts — and off for what proxying would break: mail hosts and in-zone MX targets, underscored names (_acme-challenge, DKIM keys) and labels that name a non-HTTP service (ssh, vpn, ns1, …), each with a warning saying why. A row that would overwrite an existing record mirrors that record's setting, because an import never changes it. Change any of it per row in the commit body.

This works before the domain is active, including an external-DNS domain that is not verified yet: the owner can review and commit its scan at once. Only the automatic commit waits for verification.

Idempotent. If the domain already has an open session it comes back with 200 and no new scan is started, so a double click or a retry cannot start a second sweep.

Path parameters

  • domainstringrequiredpath

    The domain name (for example example.com) — not a numeric id.

Request body

Content type: application/json

  • sourcestring

    Recorded on the session so support can answer "where did this import come from" months later. An unrecognised value falls back to manual_scan.

    One of: "manual_scan" "domain_create" "zone_file"
    Default: "manual_scan"

Responses

200An open session already existed and is returned unchanged. No new scan was started.

Content type: application/json

  • idinteger
  • domain_idinteger
  • statusstring

    scanning — the sweep is running. ready — waiting for a commit or a discard. committing — a commit is in flight and holds the claim. auto_committed — nobody answered before auto_commit_at and the records were written anyway, rather than leaving the zone empty. superseded — replaced by a rescan.

    One of: "scanning" "ready" "committing" "committed" "auto_committed" "discarded" "superseded" "failed"
  • sourcestring
    One of: "manual_scan" "domain_create" "zone_file"
  • clientstring

    What started the session. An API key has nowhere to show a review screen, so adding a domain with a key still imports automatically; staged review is the panel path.

    One of: "panel" "api"
  • methodstring

    Which branch found the records — a full zone transfer, or a resolver sweep of common names. The first support question about a bad import is where the records came from.

    One of: "axfr" "scan"
  • source_nsarray of string

    The nameservers this scan read. Stored because a rescan has to reuse them: once the domain is delegated to NSIN, resolving it again finds our own — still empty — zone.

  • errorstring

    Why the scan, or the last commit attempt, failed.

  • started_atstring (date-time)
  • finished_atstring (date-time)

    When the scan finished and the session turned ready.

  • committed_atstring (date-time)
  • auto_commit_atstring (date-time)

    When an unanswered ready session commits itself. Cleared by a commit, a discard or a rescan.

  • countsImportSessionCounts

    The header line of a review screen.

    Fields
    • totalinteger
    • preselectedinteger

      Rows a mode "all" commit would write.

    • proxiedinteger
    • warningsinteger

      Rows carrying at least one warning.

  • recordsarray of ImportSessionRecord

    Empty while status is scanning.

    Item fields
    • idinteger

      Staging row id. It is not a record id — it exists only until the session is committed or discarded.

    • namestring
    • typestring
      One of: "A" "AAAA" "CNAME" "ANAME" "NS" "TXT" "MX" "SRV" "PTR" "CAA" "TLSA" "SSHFP" "URI"
    • destinationstring
    • ttlinteger
    • mx_priorityinteger
    • proxiedboolean

      Whether this row would be proxied — and, because a mode: "all" commit (the unattended auto_commit_at one included) writes exactly this flag, what happens to the row if nobody answers.

      It is true for the rows the edge can carry: a proxyable type on a routable origin. It is false wherever proxying would take a service away rather than accelerate it — a mail host, the target of an in-zone MX, an underscored name (_acme-challenge, selector1._domainkey), or a label that names something that does not speak HTTP (ssh, vpn, mysql, ns1, …). Those rows carry a mail_host or non_web_service warning saying so.

      An overwrite row mirrors the record it would replace: an import refreshes content and TTL and never changes an existing record's proxy setting.

    • statusstring

      overwrite means the domain already holds a record with this name and type and it would be replaced. TXT is never overwrite: many TXT records share one name.

      One of: "new" "overwrite" "unsupported"
    • existing_idinteger

      The record that would be replaced. Set when status is overwrite.

    • proxy_eligibleboolean

      Whether this row could be proxied if you switched it on — a proxyable type pointing at an address the edge will accept.

    • preselectedboolean

      Whether the row is included when you commit with mode: "all". Rows carrying an error warning arrive unticked.

    • warningsarray of ImportWarning
      Item fields
      • codestring
        One of: "unsupported" "not_proxyable_type" "private_address" "points_at_nsin" "proxy_ineligible_origin" "mail_host" "non_web_service" "overwrite" "wildcard"
      • severitystring

        Only error rows arrive unticked (preselected: false) — they are the ones NSIN cannot import at all.

        One of: "info" "warning" "error"
      • messagestring
    • reasonstring

      The first warning's message, kept for clients written against the flat scan preview.

json
{
  "id": 0,
  "domain_id": 0,
  "status": "scanning",
  "source": "manual_scan",
  "client": "panel",
  "method": "axfr",
  "source_ns": [
    "string"
  ],
  "error": "string",
  "started_at": "2026-01-01T00:00:00Z",
  "finished_at": "2026-01-01T00:00:00Z",
  "committed_at": "2026-01-01T00:00:00Z",
  "auto_commit_at": "2026-01-01T00:00:00Z",
  "counts": {
    "total": 0,
    "preselected": 0,
    "proxied": 0,
    "warnings": 0
  },
  "records": [
    {
      "id": 0,
      "name": "string",
      "type": "A",
      "destination": "string",
      "ttl": 0,
      "mx_priority": 0,
      "proxied": true,
      "status": "new",
      "existing_id": 0,
      "proxy_eligible": true,
      "preselected": true,
      "warnings": [
        {
          "code": "unsupported",
          "severity": "info",
          "message": "string"
        }
      ],
      "reason": "string"
    }
  ]
}
202A new session was created and its scan is running. status is scanning and records stays empty until it finishes.

Content type: application/json

  • idinteger
  • domain_idinteger
  • statusstring

    scanning — the sweep is running. ready — waiting for a commit or a discard. committing — a commit is in flight and holds the claim. auto_committed — nobody answered before auto_commit_at and the records were written anyway, rather than leaving the zone empty. superseded — replaced by a rescan.

    One of: "scanning" "ready" "committing" "committed" "auto_committed" "discarded" "superseded" "failed"
  • sourcestring
    One of: "manual_scan" "domain_create" "zone_file"
  • clientstring

    What started the session. An API key has nowhere to show a review screen, so adding a domain with a key still imports automatically; staged review is the panel path.

    One of: "panel" "api"
  • methodstring

    Which branch found the records — a full zone transfer, or a resolver sweep of common names. The first support question about a bad import is where the records came from.

    One of: "axfr" "scan"
  • source_nsarray of string

    The nameservers this scan read. Stored because a rescan has to reuse them: once the domain is delegated to NSIN, resolving it again finds our own — still empty — zone.

  • errorstring

    Why the scan, or the last commit attempt, failed.

  • started_atstring (date-time)
  • finished_atstring (date-time)

    When the scan finished and the session turned ready.

  • committed_atstring (date-time)
  • auto_commit_atstring (date-time)

    When an unanswered ready session commits itself. Cleared by a commit, a discard or a rescan.

  • countsImportSessionCounts

    The header line of a review screen.

    Fields
    • totalinteger
    • preselectedinteger

      Rows a mode "all" commit would write.

    • proxiedinteger
    • warningsinteger

      Rows carrying at least one warning.

  • recordsarray of ImportSessionRecord

    Empty while status is scanning.

    Item fields
    • idinteger

      Staging row id. It is not a record id — it exists only until the session is committed or discarded.

    • namestring
    • typestring
      One of: "A" "AAAA" "CNAME" "ANAME" "NS" "TXT" "MX" "SRV" "PTR" "CAA" "TLSA" "SSHFP" "URI"
    • destinationstring
    • ttlinteger
    • mx_priorityinteger
    • proxiedboolean

      Whether this row would be proxied — and, because a mode: "all" commit (the unattended auto_commit_at one included) writes exactly this flag, what happens to the row if nobody answers.

      It is true for the rows the edge can carry: a proxyable type on a routable origin. It is false wherever proxying would take a service away rather than accelerate it — a mail host, the target of an in-zone MX, an underscored name (_acme-challenge, selector1._domainkey), or a label that names something that does not speak HTTP (ssh, vpn, mysql, ns1, …). Those rows carry a mail_host or non_web_service warning saying so.

      An overwrite row mirrors the record it would replace: an import refreshes content and TTL and never changes an existing record's proxy setting.

    • statusstring

      overwrite means the domain already holds a record with this name and type and it would be replaced. TXT is never overwrite: many TXT records share one name.

      One of: "new" "overwrite" "unsupported"
    • existing_idinteger

      The record that would be replaced. Set when status is overwrite.

    • proxy_eligibleboolean

      Whether this row could be proxied if you switched it on — a proxyable type pointing at an address the edge will accept.

    • preselectedboolean

      Whether the row is included when you commit with mode: "all". Rows carrying an error warning arrive unticked.

    • warningsarray of ImportWarning
      Item fields
      • codestring
        One of: "unsupported" "not_proxyable_type" "private_address" "points_at_nsin" "proxy_ineligible_origin" "mail_host" "non_web_service" "overwrite" "wildcard"
      • severitystring

        Only error rows arrive unticked (preselected: false) — they are the ones NSIN cannot import at all.

        One of: "info" "warning" "error"
      • messagestring
    • reasonstring

      The first warning's message, kept for clients written against the flat scan preview.

json
{
  "id": 0,
  "domain_id": 0,
  "status": "scanning",
  "source": "manual_scan",
  "client": "panel",
  "method": "axfr",
  "source_ns": [
    "string"
  ],
  "error": "string",
  "started_at": "2026-01-01T00:00:00Z",
  "finished_at": "2026-01-01T00:00:00Z",
  "committed_at": "2026-01-01T00:00:00Z",
  "auto_commit_at": "2026-01-01T00:00:00Z",
  "counts": {
    "total": 0,
    "preselected": 0,
    "proxied": 0,
    "warnings": 0
  },
  "records": [
    {
      "id": 0,
      "name": "string",
      "type": "A",
      "destination": "string",
      "ttl": 0,
      "mx_priority": 0,
      "proxied": true,
      "status": "new",
      "existing_id": 0,
      "proxy_eligible": true,
      "preselected": true,
      "warnings": [
        {
          "code": "unsupported",
          "severity": "info",
          "message": "string"
        }
      ],
      "reason": "string"
    }
  ]
}
401Missing, malformed, revoked or expired API key. (A valid key whose account has been deactivated gets 403 account is inactive instead.)

Content type: application/json

  • errorstringrequired

    Human-readable description of what went wrong.

json
{
  "error": "invalid API key"
}
403The key is read-only, your role on the domain lacks the required permission, or the domain's plan does not include the feature.

Content type: application/json

  • errorstringrequired

    Human-readable description of what went wrong.

json
{
  "error": "read-only API key"
}
404No such domain, or it is not visible to this account. Domains you cannot access are reported as not found rather than forbidden.

Content type: application/json

  • errorstringrequired

    Human-readable description of what went wrong.

json
{
  "error": "read-only API key"
}
409The domain is disabled or banned.

Content type: application/json

  • errorstringrequired

    Human-readable description of what went wrong.

json
{
  "error": "domain is disabled"
}
429A scan for this domain ran less than five minutes ago. One scan is up to roughly 1100 outbound DNS queries, so it is throttled per domain — this is separate from, and additional to, the per-key rate limit. Wait retry_after_seconds and retry.

Content type: application/json

  • errorstringrequired

    Human-readable description of what went wrong.

  • retry_after_secondsinteger

    Seconds left on this domain's five-minute scan window.

json
{
  "error": "a scan for this domain ran a moment ago; please wait before scanning again",
  "retry_after_seconds": 173
}

Example request

bash
curl -X POST "https://api.nsin.ir/domains/example.com/records/import-sessions" \
  -H "Authorization: Bearer $NSIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "source": "manual_scan"
}'
esc