ArcNautical Public vessel check API

Unauthenticated hull screening. IP rate-limited, cached, and not retained against a customer. Requires no key and no account — this is the endpoint the documentation leads with.

Operations 1

GET /api/v1/vessels/{imo}/check Screen a hull by IMO (no key) #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/arcnautical-com:arcnautical-com-public-vessel-check-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

arcnautical-com-public-vessel-check-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: ArcNautical Public vessel check API
  version: v1
  description: 'Vessel sanctions screening (single hull, batches of 50, standing monitors, fleets, cargo), voyage risk scoring, corridor monitoring, signed webhooks, exports and usage reporting. One keyless GET screens a hull; everything else runs on a self-serve key.


    Limits and expiry are entitlement-specific. Use GET /api/v1/usage to read the authenticated key environment, quotas, batch limits, and live-monitor capacity.


    The API exposes normalized route and risk outputs only. It does not expose raw third-party feeds, internal scoring weights, or AI-generated briefing prose.


    FIELD NAMES ARE snake_case, AND UNKNOWN FIELDS ARE REFUSED. A body property this API does not read returns 422 with field_violations naming it and listing the accepted keys — it is never accepted and ignored. A camelCase spelling of a real field is named explicitly in the violation. Note that the keyless GET /api/v1/vessels/{imo}/check RESPONSE is camelCase; that is a different contract and does not carry over to request bodies here.


    CORS: preflight (OPTIONS) returns Access-Control-Allow-Origin: * with Allow-Methods and an echo of your Access-Control-Request-Headers, so this API is callable from a browser. Authorization is not a CORS-safelisted header, so every cross-origin call — including GET — will preflight. Credentials are a bearer key you supply, never an ambient cookie, which is why * is both correct and permitted.


    HTTP conventions: HEAD is accepted anywhere GET is and returns the same headers with no body. A valid path reached with the wrong method returns 405 with an Allow header, never 404. Content-Type may be application/json, text/plain, or absent — text/plain is accepted deliberately because that is what fetch() sets for a string body — but a header describing a genuinely different format (form-encoded, multipart, XML) returns 415 rather than being parsed as JSON anyway. limit is validated, never clamped — 0, a negative, a fraction, or a value above the maximum returns 422 rather than being silently adjusted. A path under /api/vN for a version this service does not publish returns a JSON envelope with code unsupported_api_version, so it is distinguishable from the HTML a network layer would return.'
  x-api-version: '2026-10-29'
  contact:
    name: ArcNautical
    url: https://arcnautical.com/developers/
    email: admin@arcnautical.com
  termsOfService: https://arcnautical.com/terms.html
  x-logo:
    url: https://arcnautical.com/logo-512.png
    altText: ArcNautical
servers:
- url: https://arcnautical.com
  description: Production
- url: http://localhost:3001
  description: Local development
tags:
- name: Public vessel check
  description: Unauthenticated hull screening. IP rate-limited, cached, and not retained against a customer. Requires no key and no account — this is the endpoint the documentation leads with.
paths:
  /api/v1/vessels/{imo}/check:
    get:
      operationId: checkVesselPublic
      summary: Screen a hull by IMO (no key)
      description: Screens one hull against sanctions, ownership opacity, and vetting. Requires NO API key and no account. Rate-limited to 100 requests/hour per IP and cached for one hour. The response reports X-RateLimit-Limit, -Remaining and -Reset so a client can pace itself without being refused first. Results are not retained against a customer and carry no quota. This screens a VESSEL, not a voyage — voyage scoring is POST /api/v1/voyage-assessments and needs a key.
      tags:
      - Public vessel check
      security: []
      parameters:
      - name: imo
        in: path
        required: true
        description: 'IMO number. Accepts 9811000 or IMO9811000 (URL-encode a space: IMO%209811000), the same forms POST /api/v1/screenings accepts. Anything without exactly one 7-digit number in it is refused with 400 `invalid_imo`, which is not counted against the hourly allowance.'
        schema:
          type: string
          example: '9811000'
      responses:
        '200':
          description: Screening verdict.
          headers:
            X-RateLimit-Limit:
              description: Requests allowed per hour, per IP.
              schema:
                type: integer
            Cache-Control:
              description: Verdicts are cached for one hour. INCOMPLETE is never cached.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicVesselCheck'
              example:
                imo: '9274446'
                sanctions:
                  status: RED
                  detail: 4 confirmed matches on vessel identifier.
                  detailKey: verdict.sanctions.confirmed_match_other
                  detailParams:
                    count: 4
                  coverageComplete: true
                  coverageGaps: []
                  identityScope: current-name-and-identifiers
                ownership:
                  opacity: MEDIUM
                  score: 40
                vetting:
                  grade: E
                  score: 90
                  status: unacceptable
                assessed: true
                checkedAt: '2026-09-12T08:10:40.616Z'
                provider: ArcNautical
                fullReport: https://arcnautical.com/check?imo=9274446
                docs: https://github.com/arcnautical/vessel-check-api
                tier: keyless
                tierDetail: 'Verdict summary only. A self-serve API key is free — no card — and returns the full record: every sanctions match with its source list, programme and confidence class, each source reported separately, retained as a citable screening record; 2,000 screenings on its own allowance (one-time on a new account), not this per-address limit. Mint one at https://arcnautical.com/get-a-key?from=keyless. This endpoint ignores credentials.'
                fullRecord: https://arcnautical.com/developers
        '400':
          description: '`invalid_imo`: the path segment does not contain exactly one 7-digit number. The message says what is wrong with the value sent (no digits, too few, or which valid IMO an 8-digit value contains). Not counted against the hourly allowance, and it carries the X-RateLimit headers. Before 2026-09-30 a value containing a letter was answered 404 `not_found` by the edge instead.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicCheckError'
        '429':
          description: 'Rate limit exceeded: the per-IP hourly allowance, or a burst refused before it. Also code `concurrency_limit`: more than 2 checks from this address waiting for a screening slot at once; send the next when one returns. All carry Retry-After and the {code, message, request_id} envelope.'
        '500':
          description: Screening failed.
        '503':
          description: '`keyless_busy`: at most 4 hulls that are not already cached are screened at once across every keyless caller, and this check could not be seated within 10s. It is not counted against the hourly allowance; retry after Retry-After. A hull anyone checked in the last hour answers from cache and never waits. `keyless_paused`: keyless checks are temporarily paused under unusual load; Retry-After says when to try again. Neither applies to the keyed lane (POST /api/v1/screenings), which runs separately.'
components:
  schemas:
    PublicCurrentContext:
      type: object
      description: Freshness of INCIDENT CONTEXT for this hull. Distinct from sanctions.coverageComplete, which is about sanctions-source coverage.
      properties:
        coverage:
          type: string
          enum:
          - fresh
          - refreshing
          - stale
          - unavailable
          description: How recently incident context was refreshed for this hull.
        checkedAt:
          type:
          - string
          - 'null'
          format: date-time
        lookbackStart:
          type: string
        incidents:
          type: array
          items:
            type: object
    PublicOwnership:
      type: object
      description: Ownership opacity. HIGH is the dark-fleet signature (no LEI plus flag of convenience). Both fields are NULL when the top-level `assessed` is false.
      properties:
        opacity:
          type:
          - string
          - 'null'
          enum:
          - LOW
          - MEDIUM
          - HIGH
          - null
        score:
          type:
          - integer
          - 'null'
          minimum: 0
          maximum: 100
    PublicCheckError:
      type: object
      description: 'Error body for the keyless check endpoint: the {code, message, request_id} shape every other error uses, plus `usage` and a worked `example`. (Until 2026-09-07 it was {error, usage, example}; this schema kept describing that shape until 2026-09-30.)'
      required:
      - code
      - message
      - request_id
      properties:
        code:
          type: string
          enum:
          - invalid_imo
          description: Branch on this, never on the message.
        message:
          type: string
        request_id:
          type: string
          description: Also in the X-Request-Id header. Quote it to support.
        usage:
          type: string
          example: GET /api/v1/vessels/{imo}/check
        example:
          type: string
          example: GET /api/v1/vessels/9034690/check
    PublicSanctionsVerdict:
      type: object
      description: 'Sanctions outcome plus how complete the SOURCE COVERAGE was. Note: coverageComplete/coverageGaps describe which sanctions lists were reachable. That is a different axis from currentContext.coverage, which describes incident-context freshness. Same word, different meaning.'
      required:
      - status
      - detail
      - coverageComplete
      properties:
        status:
          type: string
          enum:
          - RED
          - AMBER
          - INCOMPLETE
          - GREEN
          description: 'RED = confirmed match. AMBER = possible match needing review. GREEN = no match against the sources reached. INCOMPLETE = no match, but the screen cannot be attested as clear: a CORE source (OFAC SDN, OpenSanctions, EU FSD) could not be loaded, OR no source could name the hull (the identifiers were screened, a name was not), OR the IMO fails its check digit and belongs to no vessel. `detail` says which. INCOMPLETE FAILS CLOSED — it must never be read or rendered as clear, and it is deliberately never cached.'
        detail:
          type: string
          description: Human-readable explanation of the verdict.
        coverageComplete:
          type: boolean
          description: True when every sanctions source loaded. A GREEN with coverageComplete=false means "clear against the sources we reached, one supplementary source was unavailable" — treat it as a distinct state and re-screen, not as a clean pass.
        coverageGaps:
          type: array
          description: 'Display names of sources that did NOT load. One of: OFAC SDN, OpenSanctions, EU FSD, UN Consolidated, UK OFSI.'
          items:
            type: string
        detailKey:
          type: string
          description: Stable message key behind `detail` (e.g. verdict.sanctions.confirmed_match_other), for a client that renders its own wording or another language. `detail` is the English rendering of this key with `detailParams`.
        detailParams:
          type: object
          description: Parameters for `detailKey`, such as the match count.
          additionalProperties: true
        identityScope:
          type: string
          enum:
          - current-name-and-identifiers
          description: 'What the lists were matched against: the identifiers and current name of the hull, plus the other names our identity sources hold for it (named in `detail`). A former name none of our sources holds is not screened, so a hull renamed after a designation under a name we never saw can still read GREEN. The value keeps its original spelling for compatibility.'
    PublicVetting:
      type: object
      description: 'Vetting grade. Both `grade` and `score` are NULL when the top-level `assessed` is false — there is no stand-in grade, and a letter beside `assessed: false` gets read as a letter. They were declared non-nullable here until 2026-09-06 while the endpoint already returned null, so a client generated from this document could not deserialise the response.'
      properties:
        grade:
          type:
          - string
          - 'null'
          enum:
          - A
          - B
          - C
          - D
          - E
          - null
        score:
          type:
          - integer
          - 'null'
          minimum: 0
          maximum: 100
        status:
          type: string
          enum:
          - acceptable
          - marginal
          - substandard
          - unacceptable
          - not_assessed
    PublicVesselCheck:
      type: object
      description: Screening verdict for one hull. Returned by the keyless GET /api/v1/vessels/{imo}/check.
      required:
      - imo
      - sanctions
      - ownership
      - vetting
      - assessed
      - checkedAt
      properties:
        imo:
          type: string
          description: The seven-digit IMO that was screened.
        vesselName:
          type:
          - string
          - 'null'
          description: The name our sources hold for this hull, so you can confirm WHICH vessel was screened. Null when no source names it.
        alsoRecordedAs:
          type: array
          description: Other names our sources hold for this hull, each with the source that saw it. Every one was screened on this call.
          items:
            type: object
            properties:
              name:
                type: string
              source:
                type: string
        identityBasis:
          type: string
          enum:
          - sources
          - user
          - none
          description: sources = our records name the hull; none = nothing named it (the identifiers were screened, a name was not).
        checkDigitValid:
          type: boolean
          description: False when the IMO fails its check digit — almost always a typo. The hull is still screened; treat a clear result with suspicion.
        sanctions:
          $ref: '#/components/schemas/PublicSanctionsVerdict'
        ownership:
          $ref: '#/components/schemas/PublicOwnership'
        ownershipFactors:
          type:
          - array
          - 'null'
          description: 'What ownership.score is made of: the lines it is the sum of (clamped to 0-100), each with its points, so "no LEI record on an open register" can be told from a hidden owner. Null when the hull was not assessed, and null when the lines would not add up to the score shown (we never send a breakdown that disagrees with its total). A record cached before 2026-10-02 omits it. Labels are sentences for a person to read, not codes: display them, do not parse them.'
          items:
            type: object
            required:
            - label
            - points
            properties:
              label:
                type: string
              points:
                type: number
                description: Contribution to ownership.score. Negative for a relief.
        vetting:
          $ref: '#/components/schemas/PublicVetting'
        assessed:
          type: boolean
          description: 'False means the hull was not assessed: ownership opacity/score and the vetting grade/score are then NULL and vetting.status is "not_assessed" — there is no stand-in value. Render "not assessed", never a grade. The sanctions verdict is unaffected.'
        currentContext:
          $ref: '#/components/schemas/PublicCurrentContext'
        checkedAt:
          type: string
          format: date-time
        provider:
          type: string
        fullReport:
          type: string
          description: Human-readable report URL for this hull.
        docs:
          type: string
          description: Documentation URL for this endpoint.
        tier:
          type: string
          enum:
          - keyless
          description: Always "keyless" here. Says which tier produced the body, so a client that also holds a key can tell the two record shapes apart.
        tierDetail:
          type: string
          description: What this tier does NOT contain, that a self-serve key is free, what it grants, and where to mint one. Rides on every response because this endpoint is the one channel a keyless integration is guaranteed to read.
        fullRecord:
          type: string
          description: Where the authenticated screening record is documented.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: arc_live_... or arc_test_...
      description: 'Preferred authentication. Use Authorization: Bearer arc_live_... or arc_test_....'
    legacyApiKey:
      type: apiKey
      in: header
      name: X-ArcNautical-Key
      description: Compatibility header for customers that cannot send Bearer tokens.
externalDocs:
  description: Human quickstart, integration rules, and endpoint guide
  url: https://arcnautical.com/developers/