🐍 PythonAcademic / Publishing

Academic Publishing Validation

Validate DOI, ORCID, ISBN and ISSN identifiers in Python using asyncio.gather for concurrent API calls. Enrich submissions with CrossRef metadata and ORCID author profiles.

Also available in Node.js

1. Academic identifiers covered

IdentifierEndpointUse case
DOI/v0/doiDigital Object Identifier — links to published works
ORCID/v0/orcidOpen researcher & contributor ID
ISBN/v0/isbnInternational Standard Book Number
ISSN/v0/issnInternational Standard Serial Number for journals

2. Validate DOI

Use await iv.doi(value) (SDK) or GET /v0/doi?value=….

{
  "valid": true,
  "doi": "10.1000/xyz123",
  "prefix": "10.1000",
  "suffix": "xyz123",
  "registrantCode": "1000",
  "registrant": "International DOI Foundation",
  "url": "https://doi.org/10.1000/xyz123",
  "metadata": {
    "found": true,
    "title": "Sample Article Title",
    "authors": ["Smith, J.", "Doe, A."],
    "publisher": "Example Publisher",
    "type": "journal-article",
    "issued": "2023-04"
  }
}
  1. Check valid — structural DOI format check
  2. Check metadata.found for CrossRef registration
  3. Use metadata.title and metadata.authors for auto-fill
  4. Verify metadata.type matches expected content type

3. Validate ORCID

Use await iv.orcid(value) (SDK) or GET /v0/orcid?value=….

{
  "valid": true,
  "formatted": "0000-0002-1825-0097",
  "uri": "https://orcid.org/0000-0002-1825-0097",
  "profile": {
    "found": true,
    "givenNames": "Josiah",
    "familyName": "Carberry",
    "organization": "Brown University"
  }
}
  1. Check valid — ISO 27729 checksum
  2. Check profile.found — registered with ORCID registry
  3. Use profile data for author name auto-fill if profile is public
ℹ️Many researchers set their ORCID profile to private. profile.found = false does not mean the ORCID is invalid — only that public profile data is unavailable.

4. Validate ISBN

Use await iv.isbn(value) (SDK) or GET /v0/isbn?value=….

{
  "valid": true,
  "format": "ISBN-13",
  "isbn10": "0306406152",
  "isbn13": "9780306406157"
}
  1. Check valid and format
  2. Store both isbn10 and isbn13 for cross-referencing

5. Validate ISSN

Use await iv.issn(value) (SDK) or GET /v0/issn?value=….

{
  "valid": true,
  "issn": "20493630",
  "normalized": "2049-3630"
}
  1. Check valid — mod-11 checksum including X digit
  2. Store normalized (XXXX-XXXX) as canonical form

6. Parallel validation with asyncio.gather

Dynamically build the task dict based on which identifiers the submission includes, then run all concurrently.

import asyncio
from isvalid_sdk import IsValidConfig, create_client

config = IsValidConfig(api_key="YOUR_API_KEY")
iv = create_client(config)

async def validate_academic_submission(submission: dict):
    tasks = {}
    if submission.get("doi"):
        tasks["doi"] = iv.doi(submission["doi"])
    if submission.get("orcid"):
        tasks["orcid"] = iv.orcid(submission["orcid"])
    if submission.get("isbn"):
        tasks["isbn"] = iv.isbn(submission["isbn"])
    if submission.get("issn"):
        tasks["issn"] = iv.issn(submission["issn"])

    results = dict(zip(tasks.keys(), await asyncio.gather(*tasks.values())))

    doi_r = results.get("doi")
    if doi_r and doi_r.valid and doi_r.metadata:
        print(f"Title: {doi_r.metadata.title}")
        print(f"Publisher: {doi_r.metadata.publisher}")

    orcid_r = results.get("orcid")
    if orcid_r and orcid_r.valid and orcid_r.profile:
        print(f"Author: {orcid_r.profile.given_names} {orcid_r.profile.family_name}")

    return results

async def main():
    result = await validate_academic_submission({
        "doi":   "10.1000/xyz123",
        "orcid": "0000-0002-1825-0097",
        "issn":  "2049-3630",
    })
    for key, val in result.items():
        print(f"{key}: valid={val.valid}")

asyncio.run(main())

7. Edge cases

DOI valid but not registered in CrossRef

⚠️DOIs can be structurally valid before being registered. For manuscript submission systems, require metadata.found = True only for references, not for the manuscript being submitted.
doi = await iv.doi(value)
if doi.valid and not doi.metadata.found:
    # DOI format is correct, but not yet indexed by CrossRef
    warn("DOI not yet resolvable — proceed with caution")

ORCID check digit — X character

💡The last digit of an ORCID can be the letter X (representing 10 in the ISO 7064 MOD 11-2 algorithm). Always accept both numeric and X as the final character.
# IsValid handles this automatically, but ensure your DB schema
# stores ORCID as VARCHAR(19), not a numeric type
orcid = await iv.orcid("0000-0001-5109-3700")  # ends in 0
orcid_x = await iv.orcid("0000-0002-1694-233X")  # ends in X — still valid

ISBN-10 vs ISBN-13 normalization

isbn = await iv.isbn(user_input)
if isbn.valid:
    # Always store the ISBN-13 form as the canonical identifier
    canonical = isbn.isbn13

8. Summary checklist

Run all identifier calls with asyncio.gather
Require metadata.found for reference DOIs
Handle private ORCID profiles gracefully
Store ISBN-13 as canonical form
Normalize ISSN to XXXX-XXXX format
Accept ORCID check digit X
Use ThreadPoolExecutor for sync fallback
Return field-level errors on validation failure

See also

Ready to integrate?

Free tier — 1,000 requests/month. No credit card required.

Get your API key →