APIs, open data & integration

A relative API link is not text to append to a domain

The short answer

Establish the base URI first. Resolution identifies a destination; it does not authorize visiting that destination.

What fits your situation? Answer 30 short questions →

Make a link two decisions

A response can contain a relative resource reference. The consumer must first determine which URI it identifies and then whether that destination fits the agreed follow-up scope. Separating those decisions lets you assess a link without requesting it. That is useful for detail views and downloads even when there is no pagination.

RFC 3986 defines resolution against a base and distinguishes path, query, and authority.[3] A query-only reference is therefore not the same as a new path segment. The local exercise below explicitly selects the supplied base URI; a representation with its own base mechanism first requires interpretation of that format.

A destination card without network traffic

  • Base: the URI established as the base for this representation.
  • Literal reference: preserve path and query meaning; do not lowercase them.
  • Resolved URI: use a resolution function rather than string concatenation.
  • Follow-up state: ready only means the local origin comparison passes; hold requires a separate decision.

The invented base below ends in items/7 and includes lang=nl. The reference 8 replaces the last path segment. ?lang=en retains that path and replaces the query. /health goes to the origin root. An authority reference to another host is resolved but held. None of the example hosts is contacted.

Complete local exercise and literal output

Use Copy code to copy the complete code exactly. Manual selection may omit the final newline.

import json
from urllib.parse import urljoin, urlsplit

def origin(u):
    p = urlsplit(u)
    if p.scheme != "https" or not p.hostname or p.username is not None or p.password is not None:
        raise ValueError("unsupported authority")
    return (p.scheme, p.hostname.lower(), p.port if p.port is not None else 443)

def destination(base, reference):
    if type(reference) is not str or any(ord(c) in range(33) for c in reference):
        raise ValueError("invalid reference for this example")
    base_origin = origin(base)
    resolved = urljoin(base, reference)
    target_origin = origin(resolved)
    if urlsplit(resolved).fragment:
        return {"state": "hold_fragment", "url": resolved}
    return {"state": "ready" if target_origin == base_origin else "hold_external", "url": resolved}

# Reserved example hostnames, never contacted.
base = "https://api.example.test/v1/items/7?lang=nl"
cases = ["8", "?lang=en", "/health", "//other.example.test/x", "#part"]
results = [destination(base, x) for x in cases]
assert results[0]["url"] == "https://api.example.test/v1/items/8"
assert results[1]["url"] == "https://api.example.test/v1/items/7?lang=en"
assert [x["state"] for x in results] == ["ready", "ready", "ready", "hold_external", "hold_fragment"]
assert destination(base, "https://api.example.test:443/x")["state"] == "ready"
assert destination(base, "https://api.example.test:0/x")["state"] == "hold_external"
for bad in ["http://api.example.test/x", "https://user@api.example.test/x", "bad link"]:
    try:
        destination(base, bad)
    except ValueError:
        pass
    else:
        raise AssertionError("unsupported reference accepted")
print(json.dumps({"destinations": results, "rejected": 3}, sort_keys=True))

{"destinations": [{"state": "ready", "url": "https://api.example.test/v1/items/8"}, {"state": "ready", "url": "https://api.example.test/v1/items/7?lang=en"}, {"state": "ready", "url": "https://api.example.test/health"}, {"state": "hold_external", "url": "https://other.example.test/x"}, {"state": "hold_fragment", "url": "https://api.example.test/v1/items/7?lang=nl#part"}], "rejected": 3}

The three rejected cases use HTTP, user information in the authority, or a space in the reference. An explicit HTTPS port 443 counts as the same origin in this exercise. Fragments receive a separate hold_fragment: the code does not invent how a client should handle an intra-document reference. These labels are policy outcomes, not HTTP statuses.

What ready emphatically does not mean

A same-origin URI can still fall outside the agreed resource family. This exercise has no path allowlist, redirect policy, DNS check, or HTTP client and is not a general URL safety validator. It also does not handle other URI schemes, full URI normalization, or every form of Unicode. Use it only to practice separating resolution from the follow-up decision.

For a real handoff, record the base source, reference field, resolved destination, and exception owner. Confirm access and source use separately before adding requests. This source-grounded offline exercise proves neither availability, a complete dataset, nor an access right. It remains a draft with factual, originality, and translation review pending.

Sources & evidence

  1. Original source title: RFC 3986 — Uniform Resource Identifier (URI): Generic Syntax

    Accessed: 2026-10-18T22:58:30.463656+00:00

A guide for your situation

Your answers stay in this page until you explicitly submit them. Unsaved answers disappear on reload.

Saving is not configured. No answers or contact details are submitted or saved.

Which integration step helps you move forward?

30 short questions about APIs and connecting data. Selecting an answer moves to the next question. You can go back, change answers, or skip. This is a practical reading route, not a scientific test.

JavaScript is required for this guide.

Contact & corrections

Send a question or report a correction using this form.

Only after Send are your name, email, subject and message sent to the contact service to handle your message. This is not a newsletter signup. Unsent text disappears on reload. Privacy information

Preview: sending is not configured. This form does not send or save anything.

Your message — all fields are required