{
 "openapi": "3.1.0",
 "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
 "info": {
  "title": "teloman API",
  "version": "1.0.0",
  "summary": "Phones in Norway: eSIM, Norwegian bands, VoLTE, updates and the EU energy label, with a source per value, as static JSON.",
  "description": "Read-only, free and without keys, for AI assistants, agents and individuals. The same values as the pages on teloman.no and as the MCP server at https://teloman.no/mcp, rebuilt on every change. Every field carries its value, `source_url`, `checked` (the date it was read), `confidence` and the collector's `note`; a value of `\"unknown\"` means teloman has not found an answer and must be shown as unknown («Ukjent»), never guessed. Only models with a full, sourced page are included. Every payload carries `license`, `attribution` and `cite`; cite the page URL. Values are facts read from the manufacturers', operators' and EPREL's own pages (`license: facts`); teloman's compilation is CC BY 4.0. No prices, no ranking; teloman is run by Datamynt AS, which also owns the eSIM shop merdata.no. Content is in Norwegian. Limits per client IP (IPv6: per /64), shared with the MCP server: 60 requests a minute with bursts of 20 and 2 000 in 24 hours; over the limit the answer is 429 with `Retry-After`. Not for browsers on other websites: a request with an `Origin` header other than https://teloman.no is refused (403) and there are no CORS headers. Using the API as the data source of another website, app or service, or bulk harvesting, needs a written agreement; see the terms (https://teloman.no/bruk-dataene/#vilkar) or write to kontakt@datamynt.no. Alt om mobilen din i Norge som JSON: de samme verdiene som sidene, med kilde og dato på hvert felt.",
  "termsOfService": "https://teloman.no/bruk-dataene/#vilkar",
  "contact": {
   "name": "Datamynt AS",
   "email": "kontakt@datamynt.no",
   "url": "https://teloman.no/bruk-dataene/"
  },
  "license": {
   "name": "CC BY 4.0 for teloman's compilation; the values are facts with a source per field (license: facts)",
   "identifier": "CC-BY-4.0"
  }
 },
 "servers": [
  {
   "url": "https://teloman.no/api/v1",
   "description": "Static files, served by the rate-limited teloman server (no CORS)"
  }
 ],
 "externalDocs": {
  "url": "https://teloman.no/bruk-dataene/#api",
  "description": "Bruk dataene: API, MCP, vilkår og lisens"
 },
 "tags": [
  {
   "name": "models"
  },
  {
   "name": "hubs"
  }
 ],
 "paths": {
  "/index.json": {
   "get": {
    "operationId": "getIndex",
    "tags": [
     "models"
    ],
    "summary": "Every model with its headline answers, and every listing page",
    "responses": {
     "200": {
      "description": "The index",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        }
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        }
       },
       "RateLimit": {
        "$ref": "#/components/headers/RateLimit"
       },
       "RateLimit-Policy": {
        "$ref": "#/components/headers/RateLimitPolicy"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Index"
        }
       }
      }
     },
     "304": {
      "description": "Not modified (If-None-Match / If-Modified-Since)."
     },
     "403": {
      "$ref": "#/components/responses/Forbidden"
     },
     "404": {
      "description": "No such model or listing page."
     },
     "429": {
      "$ref": "#/components/responses/TooManyRequests"
     }
    }
   }
  },
  "/models/{slug}.json": {
   "get": {
    "operationId": "getModel",
    "tags": [
     "models"
    ],
    "summary": "Every field of one phone, with its source",
    "description": "`slug` is the last part of the page URL (teloman.no/mobil/<slug>/). Find slugs in /index.json.",
    "parameters": [
     {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
      },
      "examples": {
       "s26": {
        "value": "samsung-galaxy-s26"
       },
       "iphone": {
        "value": "apple-iphone-17"
       }
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The model payload",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        }
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        }
       },
       "RateLimit": {
        "$ref": "#/components/headers/RateLimit"
       },
       "RateLimit-Policy": {
        "$ref": "#/components/headers/RateLimitPolicy"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Model"
        }
       }
      }
     },
     "304": {
      "description": "Not modified (If-None-Match / If-Modified-Since)."
     },
     "403": {
      "$ref": "#/components/responses/Forbidden"
     },
     "404": {
      "description": "No such model or listing page."
     },
     "429": {
      "$ref": "#/components/responses/TooManyRequests"
     }
    }
   }
  },
  "/hubs/{path}.json": {
   "get": {
    "operationId": "getHub",
    "tags": [
     "hubs"
    ],
    "summary": "One listing page (eSIM, 4G Tale, updates, a brand)",
    "description": "`path` is the page's path without slashes at the ends; brand pages keep their inner slash (`merke/samsung`).",
    "parameters": [
     {
      "name": "path",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "enum": [
        "esim",
        "4g-tale",
        "oppdateringer",
        "merke/apple",
        "merke/google",
        "merke/motorola",
        "merke/nothing",
        "merke/samsung"
       ]
      }
     }
    ],
    "responses": {
     "200": {
      "description": "The listing page payload",
      "headers": {
       "ETag": {
        "schema": {
         "type": "string"
        }
       },
       "Last-Modified": {
        "schema": {
         "type": "string"
        }
       },
       "RateLimit": {
        "$ref": "#/components/headers/RateLimit"
       },
       "RateLimit-Policy": {
        "$ref": "#/components/headers/RateLimitPolicy"
       }
      },
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Hub"
        }
       }
      }
     },
     "304": {
      "description": "Not modified (If-None-Match / If-Modified-Since)."
     },
     "403": {
      "$ref": "#/components/responses/Forbidden"
     },
     "404": {
      "description": "No such model or listing page."
     },
     "429": {
      "$ref": "#/components/responses/TooManyRequests"
     }
    }
   }
  }
 },
 "components": {
  "schemas": {
   "Index": {
    "type": "object",
    "required": [
     "models",
     "hubs",
     "version"
    ],
    "properties": {
     "format": {
      "type": "integer"
     },
     "site": {
      "type": "string"
     },
     "updated": {
      "type": [
       "string",
       "null"
      ]
     },
     "version": {
      "type": "string"
     },
     "models": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "slug": {
         "type": "string"
        },
        "name": {
         "type": "string"
        },
        "brand": {
         "type": "string"
        },
        "model": {
         "type": "string"
        },
        "url": {
         "type": "string"
        },
        "api": {
         "type": "string"
        },
        "eu_model_numbers": {
         "type": "array",
         "items": {
          "type": "string"
         }
        },
        "eprel_model_identifier": {
         "type": [
          "string",
          "null"
         ]
        },
        "updated": {
         "type": "string"
        },
        "checked": {
         "type": "object",
         "properties": {
          "from": {
           "type": "string"
          },
          "to": {
           "type": "string"
          }
         }
        },
        "esim": {
         "type": "string",
         "enum": [
          "yes",
          "no",
          "unknown"
         ]
        },
        "dual_esim": {
         "type": "string",
         "enum": [
          "yes",
          "no",
          "unknown"
         ]
        },
        "bands": {
         "type": "string",
         "enum": [
          "ok",
          "partial",
          "missing",
          "unknown"
         ],
         "description": "ok = LTE 20 and 28 and 5G n28 and n78; partial = 4G yes, not every 5G band; missing = lacks a 4G band"
        },
        "sold_now": {
         "type": "string",
         "enum": [
          "yes",
          "no",
          "unknown"
         ]
        },
        "sale_status": {
         "type": "string"
        },
        "updates": {
         "type": "string"
        },
        "energy_class": {
         "type": "string"
        }
       },
       "required": [
        "slug",
        "name",
        "brand",
        "url"
       ]
      }
     },
     "hubs": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "path": {
         "type": "string"
        },
        "name": {
         "type": "string"
        },
        "title": {
         "type": "string"
        },
        "url": {
         "type": "string"
        },
        "api": {
         "type": "string"
        },
        "kind": {
         "type": "string",
         "enum": [
          "intent",
          "brand"
         ]
        },
        "brand": {
         "type": [
          "string",
          "null"
         ]
        },
        "models": {
         "type": "integer"
        },
        "updated": {
         "type": "string"
        }
       },
       "required": [
        "path",
        "name",
        "url",
        "kind"
       ]
      }
     },
     "legend": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      }
     },
     "notes": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "license": {
      "type": "object"
     },
     "license_scope": {
      "type": "string"
     },
     "attribution": {
      "type": "string"
     },
     "policy": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      }
     }
    }
   },
   "Model": {
    "type": "object",
    "properties": {
     "format": {
      "type": "integer"
     },
     "slug": {
      "type": "string"
     },
     "url": {
      "type": "string"
     },
     "api": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "brand": {
      "type": "string"
     },
     "model": {
      "type": "string"
     },
     "title": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "updated": {
      "type": "string"
     },
     "checked": {
      "type": "object",
      "properties": {
       "from": {
        "type": "string"
       },
       "to": {
        "type": "string"
       }
      }
     },
     "summary": {
      "type": "object",
      "description": "The headline answers the lists show, derived from the fields without guessing.",
      "properties": {
       "esim": {
        "type": "string",
        "enum": [
         "yes",
         "no",
         "unknown"
        ]
       },
       "dual_esim": {
        "type": "string",
        "enum": [
         "yes",
         "no",
         "unknown"
        ]
       },
       "bands": {
        "type": "string",
        "enum": [
         "ok",
         "partial",
         "missing",
         "unknown"
        ],
        "description": "ok = LTE 20 and 28 and 5G n28 and n78; partial = 4G yes, not every 5G band; missing = lacks a 4G band"
       },
       "sold_now": {
        "type": "string",
        "enum": [
         "yes",
         "no",
         "unknown"
        ]
       },
       "sale_status": {
        "type": "string"
       },
       "updates": {
        "type": "string"
       },
       "energy_class": {
        "type": "string"
       }
      }
     },
     "fields": {
      "type": "object",
      "additionalProperties": {
       "type": "object",
       "description": "One value as the page shows it, with its source. value \"unknown\" means teloman has not found an answer (show «Ukjent»); source_url is then the page that was checked.",
       "properties": {
        "value": {
         "type": [
          "string",
          "number",
          "boolean",
          "object",
          "array",
          "null"
         ]
        },
        "source_url": {
         "type": "string"
        },
        "checked": {
         "type": "string"
        },
        "confidence": {
         "type": "string",
         "enum": [
          "primary",
          "secondary",
          "unknown"
         ]
        },
        "note": {
         "type": "string"
        },
        "class": {
         "type": "string",
         "enum": [
          "fast",
          "levende"
         ]
        },
        "group": {
         "type": "string"
        },
        "about": {
         "type": "string"
        },
        "license": {
         "type": "string",
         "enum": [
          "facts"
         ]
        }
       },
       "required": [
        "value",
        "source_url",
        "checked",
        "confidence",
        "license"
       ]
      }
     },
     "caveats": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "text": {
         "type": "string"
        },
        "source_url": {
         "type": "string"
        }
       }
      }
     },
     "hubs": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "legend": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      }
     },
     "notes": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "license": {
      "anyOf": [
       {
        "type": "object",
        "required": [
         "name",
         "url"
        ],
        "properties": {
         "name": {
          "type": "string"
         },
         "url": {
          "type": "string"
         }
        }
       },
       {
        "type": "string",
        "enum": [
         "facts"
        ]
       }
      ],
      "description": "«facts»: verdier lest fra produsentens, operatørens eller EPRELs egen side, ingen lisens gis. Values read from the manufacturer's, operator's or EPREL's own page; facts with a source, no licence given."
     },
     "license_scope": {
      "type": "string"
     },
     "attribution": {
      "type": "string"
     },
     "cite": {
      "type": "string"
     }
    },
    "required": [
     "slug",
     "url",
     "name",
     "fields",
     "license",
     "attribution",
     "cite"
    ]
   },
   "Hub": {
    "type": "object",
    "properties": {
     "format": {
      "type": "integer"
     },
     "path": {
      "type": "string"
     },
     "url": {
      "type": "string"
     },
     "api": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "title": {
      "type": "string"
     },
     "kind": {
      "type": "string",
      "enum": [
       "intent",
       "brand"
      ]
     },
     "brand": {
      "type": [
       "string",
       "null"
      ]
     },
     "updated": {
      "type": "string"
     },
     "subject": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "The fields the page is about; each model below carries them."
     },
     "brand_status": {
      "anyOf": [
       {
        "type": "object",
        "description": "One value as the page shows it, with its source. value \"unknown\" means teloman has not found an answer (show «Ukjent»); source_url is then the page that was checked.",
        "properties": {
         "value": {
          "type": [
           "string",
           "number",
           "boolean",
           "object",
           "array",
           "null"
          ]
         },
         "source_url": {
          "type": "string"
         },
         "checked": {
          "type": "string"
         },
         "confidence": {
          "type": "string",
          "enum": [
           "primary",
           "secondary",
           "unknown"
          ]
         },
         "note": {
          "type": "string"
         },
         "class": {
          "type": "string",
          "enum": [
           "fast",
           "levende"
          ]
         },
         "group": {
          "type": "string"
         },
         "about": {
          "type": "string"
         },
         "license": {
          "type": "string",
          "enum": [
           "facts"
          ]
         }
        },
        "required": [
         "value",
         "source_url",
         "checked",
         "confidence",
         "license"
        ]
       },
       {
        "type": "null"
       }
      ]
     },
     "models": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "slug": {
         "type": "string"
        },
        "name": {
         "type": "string"
        },
        "url": {
         "type": "string"
        },
        "api": {
         "type": "string"
        },
        "summary": {
         "type": "object",
         "description": "The headline answers the lists show, derived from the fields without guessing.",
         "properties": {
          "esim": {
           "type": "string",
           "enum": [
            "yes",
            "no",
            "unknown"
           ]
          },
          "dual_esim": {
           "type": "string",
           "enum": [
            "yes",
            "no",
            "unknown"
           ]
          },
          "bands": {
           "type": "string",
           "enum": [
            "ok",
            "partial",
            "missing",
            "unknown"
           ],
           "description": "ok = LTE 20 and 28 and 5G n28 and n78; partial = 4G yes, not every 5G band; missing = lacks a 4G band"
          },
          "sold_now": {
           "type": "string",
           "enum": [
            "yes",
            "no",
            "unknown"
           ]
          },
          "sale_status": {
           "type": "string"
          },
          "updates": {
           "type": "string"
          },
          "energy_class": {
           "type": "string"
          }
         }
        },
        "fields": {
         "type": "object",
         "additionalProperties": {
          "type": "object",
          "description": "One value as the page shows it, with its source. value \"unknown\" means teloman has not found an answer (show «Ukjent»); source_url is then the page that was checked.",
          "properties": {
           "value": {
            "type": [
             "string",
             "number",
             "boolean",
             "object",
             "array",
             "null"
            ]
           },
           "source_url": {
            "type": "string"
           },
           "checked": {
            "type": "string"
           },
           "confidence": {
            "type": "string",
            "enum": [
             "primary",
             "secondary",
             "unknown"
            ]
           },
           "note": {
            "type": "string"
           },
           "class": {
            "type": "string",
            "enum": [
             "fast",
             "levende"
            ]
           },
           "group": {
            "type": "string"
           },
           "about": {
            "type": "string"
           },
           "license": {
            "type": "string",
            "enum": [
             "facts"
            ]
           }
          },
          "required": [
           "value",
           "source_url",
           "checked",
           "confidence",
           "license"
          ]
         }
        }
       },
       "required": [
        "slug",
        "name",
        "url",
        "fields"
       ]
      }
     },
     "legend": {
      "type": "object",
      "additionalProperties": {
       "type": "string"
      }
     },
     "notes": {
      "type": "array",
      "items": {
       "type": "string"
      }
     },
     "license": {
      "anyOf": [
       {
        "type": "object",
        "required": [
         "name",
         "url"
        ],
        "properties": {
         "name": {
          "type": "string"
         },
         "url": {
          "type": "string"
         }
        }
       },
       {
        "type": "string",
        "enum": [
         "facts"
        ]
       }
      ],
      "description": "«facts»: verdier lest fra produsentens, operatørens eller EPRELs egen side, ingen lisens gis. Values read from the manufacturer's, operator's or EPREL's own page; facts with a source, no licence given."
     },
     "license_scope": {
      "type": "string"
     },
     "attribution": {
      "type": "string"
     },
     "cite": {
      "type": "string"
     }
    },
    "required": [
     "path",
     "url",
     "name",
     "kind",
     "models",
     "license",
     "attribution",
     "cite"
    ]
   },
   "Error": {
    "type": "object",
    "required": [
     "error"
    ],
    "properties": {
     "error": {
      "type": "object",
      "required": [
       "status",
       "message"
      ],
      "properties": {
       "status": {
        "type": "integer"
       },
       "message": {
        "type": "string"
       },
       "terms": {
        "type": "string",
        "format": "uri",
        "description": "The terms of use."
       }
      }
     }
    }
   }
  },
  "headers": {
   "RateLimit": {
    "description": "Quota left per policy (draft-ietf-httpapi-ratelimit-headers), e.g. `\"minute\";r=19;t=1, \"day\";r=1999;t=86400`: r = requests left, t = seconds until it resets.",
    "schema": {
     "type": "string"
    }
   },
   "RateLimitPolicy": {
    "description": "The quotas (draft-ietf-httpapi-ratelimit-headers): `\"minute\";q=60;w=60, \"day\";q=2000;w=86400`.",
    "schema": {
     "type": "string"
    }
   },
   "RetryAfter": {
    "description": "Seconds to wait.",
    "schema": {
     "type": "integer"
    }
   }
  },
  "responses": {
   "Forbidden": {
    "description": "The request carried an `Origin` header other than https://teloman.no (a browser on another website) or an unknown `Host`. The body says why and links the terms.",
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   },
   "TooManyRequests": {
    "description": "Over the per-IP limit (60 a minute, bursts of 20, 2 000 in 24 hours). Wait `Retry-After` seconds.",
    "headers": {
     "Retry-After": {
      "$ref": "#/components/headers/RetryAfter"
     },
     "RateLimit": {
      "$ref": "#/components/headers/RateLimit"
     },
     "RateLimit-Policy": {
      "$ref": "#/components/headers/RateLimitPolicy"
     }
    },
    "content": {
     "application/json": {
      "schema": {
       "$ref": "#/components/schemas/Error"
      }
     }
    }
   }
  }
 }
}
