[{"data":1,"prerenderedAt":2005},["ShallowReactive",2],{"docs-en-\u002Fdocs\u002Fstream\u002Faisstream":3},{"id":4,"title":5,"body":6,"description":1997,"extension":1998,"meta":1999,"navigation":2000,"path":2001,"seo":2002,"stem":2003,"__hash__":2004},"docs_en\u002Fdocs\u002Fstream\u002Faisstream.md","aisstream.io compatibility",{"type":7,"value":8,"toc":1957},"minimark",[9,18,34,49,57,64,70,77,81,281,301,307,339,352,362,366,384,495,516,548,562,581,591,612,628,632,648,796,818,836,852,856,867,874,1038,1060,1066,1195,1206,1211,1284,1287,1315,1326,1330,1333,1343,1346,1350,1358,1363,1372,1384,1388,1401,1405,1436,1445,1458,1482,1494,1503,1510,1517,1527,1540,1549,1570,1589,1596,1608,1615,1619,1637,1646,1650,1656,1681,1685,1692,1711,1718,1738,1750,1757,1764,1806,1810,1827,1838,1842,1921,1953],[10,11,13,17],"h1",{"id":12},"v0-compatibility-with-aisstreamio",[14,15,16],"code",{},"\u002Fv0"," — Compatibility with aisstream.io",[19,20,21,24,25,29,30,33],"p",{},[14,22,23],{},"wss:\u002F\u002Fstream.pelyr.com\u002Fv0\u002Fstream"," reproduces the protocol of the\ndiscontinued provider aisstream.io. The goal is explicit: ",[26,27,28],"strong",{},"the switch\nshould be a search-and-replace in the client"," — the same frame shape, the\nsame field names, the same mixed case in ",[14,31,32],{},"MetaData",", the same error\nwording.",[19,35,36,37,40,41,44,45,48],{},"Anyone who wants more than a reproduction takes ",[14,38,39],{},"\u002Fv1",": named\nbounding-box fields instead of ",[14,42,43],{},"[lat,lon]",", real close codes, confirmation\nframes, a heartbeat with a loss counter, ",[14,46,47],{},"null"," where a value is missing —\nand never silent throttling.",[50,51,53,54,56],"h2",{"id":52},"the-most-important-thing-first-v0-cannot-be-verified","The most important thing first: ",[14,55,16],{}," cannot be verified",[19,58,59,60,63],{},"There is ",[26,61,62],{},"no running reference server"," for a byte-for-byte comparison.\nEverything on this page is built against the aisstream.io documentation and\narchived examples. The header of the golden tests therefore states,\nverbatim:",[65,66,67],"blockquote",{},[19,68,69],{},"This test proves the stability of our output, not compatibility with the\noriginal.",[19,71,72,73,76],{},"Every response from this endpoint carries the header\n",[14,74,75],{},"X-Pelyr-Compat: v0-partial",". This page is the complete list of what\n\"partial\" means.",[50,78,80],{"id":79},"connection-and-subscription","Connection and subscription",[82,83,84,98],"table",{},[85,86,87],"thead",{},[88,89,90,93],"tr",{},[91,92],"th",{},[91,94,95,97],{},[14,96,16],{}," here",[99,100,101,112,120,132,149,167,178,193,203,222,232,252,263],"tbody",{},[88,102,103,107],{},[104,105,106],"td",{},"Path",[104,108,109],{},[14,110,111],{},"\u002Fv0\u002Fstream",[88,113,114,117],{},[104,115,116],{},"Subprotocols",[104,118,119],{},"none",[88,121,122,125],{},[104,123,124],{},"Client's first text frame",[104,126,127,128,131],{},"within ",[26,129,130],{},"3 s",", otherwise close",[88,133,134,137],{},[104,135,136],{},"Key field",[104,138,139,148],{},[26,140,141,144,145],{},[14,142,143],{},"APIKey"," and ",[14,146,147],{},"Apikey"," — both accepted",[88,150,151,158],{},[104,152,153,154,157],{},"Key in the ",[14,155,156],{},"Authorization"," header",[104,159,160,163,164,166],{},[26,161,162],{},"no"," — frame only. On ",[14,165,39],{}," it is the primary path",[88,168,169,172],{},[104,170,171],{},"Changing the key in a later frame",[104,173,174,177],{},[26,175,176],{},"rejected"," — scope, connection limit and quota are tied to the first",[88,179,180,183],{},[104,181,182],{},"Bounding boxes",[104,184,185,188,189,192],{},[14,186,187],{},"[[[lat1,lon1],[lat2,lon2]]]"," — ",[26,190,191],{},"latitude FIRST",", corners normalised to min\u002Fmax",[88,194,195,198],{},[104,196,197],{},"Bounding boxes per subscription",[104,199,200],{},[26,201,202],{},"50",[88,204,205,210],{},[104,206,207],{},[14,208,209],{},"FiltersShipMMSI",[104,211,212,215,216,219,220],{},[26,213,214],{},"strings"," of ",[26,217,218],{},"9 characters"," each, at most ",[26,221,202],{},[88,223,224,229],{},[104,225,226],{},[14,227,228],{},"FilterMessageTypes",[104,230,231],{},"names from the original, see type table",[88,233,234,237],{},[104,235,236],{},"Data frame",[104,238,239,242,243,246,247,144,250],{},[14,240,241],{},"Message"," with ",[26,244,245],{},"exactly one"," key, plus ",[14,248,249],{},"MessageType",[14,251,32],{},[88,253,254,257],{},[104,255,256],{},"Errors",[104,258,259,260],{},"exclusively ",[14,261,262],{},"{\"error\":\"...\"}",[88,264,265,268],{},[104,266,267],{},"Close after an error",[104,269,270,273,274,276,277,280],{},[26,271,272],{},"1000",", explicitly ",[26,275,162],{}," 4xxx — but not every ",[14,278,279],{},"error"," frame closes, see below",[19,282,283,286,287,289,290,292,293,296,297,300],{},[26,284,285],{},"Why both spellings of the key?"," The original's documentation contradicts\nitself: one place says ",[14,288,143],{},", another ",[14,291,147],{},". We accept both rather\nthan guess. The wrong choice would have meant a valid key gets rejected —\nthe user would see ",[14,294,295],{},"Api Key Is Not Valid"," despite a correct key, with no\nway to tell why. If ",[26,298,299],{},"both fields carry different values"," in one frame, it\nis rejected: which one was meant cannot be decided.",[19,302,303,306],{},[26,304,305],{},"Why no 4xxx close codes?"," An aisstream client does not know them and\nlogs them as a network error. On an invalid key you therefore get exactly",[308,309,314],"pre",{"className":310,"code":311,"language":312,"meta":313,"style":313},"language-json shiki shiki-themes github-light github-dark","{\"error\":\"Api Key Is Not Valid\"}\n","json","",[14,315,316],{"__ignoreMap":313},[317,318,321,325,329,332,336],"span",{"class":319,"line":320},"line",1,[317,322,324],{"class":323},"sVt8B","{",[317,326,328],{"class":327},"sj4cs","\"error\"",[317,330,331],{"class":323},":",[317,333,335],{"class":334},"sZZnC","\"Api Key Is Not Valid\"",[317,337,338],{"class":323},"}\n",[19,340,341,342,344,345,347,348,351],{},"character for character, followed by close 1000. On ",[14,343,39],{}," there is a\n",[14,346,14],{},", a ",[14,349,350],{},"fatal"," flag and a descriptive close code for this instead.",[19,353,354,355,358,359,361],{},"A key that ",[26,356,357],{},"exists but was not sold for the stream"," is the exception to\nthat wording. It gets the same ",[14,360,279],{}," frame and the same close 1000, but a\ndifferent sentence: the key is not invalid — it grants access to the HTTPS\nAPI, just not to this one. Telling that user their key is invalid would only\nmake them issue a new one that cannot stream either.",[50,363,365],{"id":364},"message-types","Message types",[19,367,368,369,372,373,376,377,380,381,383],{},"The table lists ",[26,370,371],{},"only the types actually reachable",". For the collector this\ndepends on the feed: some collector feeds assign the type themselves and\ntherefore carry only ",[14,374,375],{},"{1, 5}",", because their upstream does not pass the\noriginal AIS type through. Others pass the real type through and carry\n",[14,378,379],{},"{1, 2, 3, 5, 18, 19, 24}"," — one of those also carries 14, another also 27.\nThe receiver carries ",[14,382,379],{},", counted against the\n906-record corpus, which contains receiver payloads only.",[82,385,386,401],{},[85,387,388],{},[88,389,390,393,396],{},[91,391,392],{},"AIS type",[91,394,395],{},"Original's frame name",[91,397,398,399],{},"Delivered by ",[14,400,16],{},[99,402,403,418,432,446,460,475],{},[88,404,405,408,413],{},[104,406,407],{},"1, 2, 3",[104,409,410],{},[14,411,412],{},"PositionReport",[104,414,415],{},[26,416,417],{},"yes",[88,419,420,423,428],{},[104,421,422],{},"5",[104,424,425],{},[14,426,427],{},"ShipStaticData",[104,429,430],{},[26,431,417],{},[88,433,434,437,442],{},[104,435,436],{},"18",[104,438,439],{},[14,440,441],{},"StandardClassBPositionReport",[104,443,444],{},[26,445,417],{},[88,447,448,451,456],{},[104,449,450],{},"19",[104,452,453],{},[14,454,455],{},"ExtendedClassBPositionReport",[104,457,458],{},[26,459,417],{},[88,461,462,465,470],{},[104,463,464],{},"24",[104,466,467],{},[14,468,469],{},"StaticDataReport",[104,471,472,474],{},[26,473,417],{}," — see below",[88,476,477,480,490],{},[104,478,479],{},"4, 9, 14, 21, 27 and all others",[104,481,482,485,486,489],{},[14,483,484],{},"BaseStationReport",", ",[14,487,488],{},"AidToNavigationReport",", …",[104,491,492,494],{},[26,493,162],{}," — no frame is built, such messages are dropped silently",[19,496,497,500,501,504,505,144,508,511,512,515],{},[26,498,499],{},"Type 24 comes in two parts."," The original models this with ",[14,502,503],{},"PartNumber",",\n",[14,506,507],{},"ReportA",[14,509,510],{},"ReportB","; our flat contract payload does not carry that split\nas a field of its own. It is therefore ",[26,513,514],{},"derived, not guessed",": part A\ncarries the ship name only, part B the ship type and call sign only — the\ninverse is unambiguous, and in the 906-record corpus above — receiver payloads\n— no type 24 record carries both at once. A message carrying none of the three\nfields cannot be assigned to a part and is dropped.",[19,517,518,521,522,525,526,529,530,533,534,536,537,539,540,543,544,547],{},[26,519,520],{},"Only the intended sub-object appears in the frame."," The original sends\nboth, including the one not meant — where it then reads ",[14,523,524],{},"ShipType: 0"," and\n",[14,527,528],{},"CallSign: \"\"",". We leave it out, because ",[14,531,532],{},"0"," is a valid ship type, and an\nempty ",[14,535,510],{}," sent along would claim the vessel had transmitted part B. A\nGo client reads a missing ",[14,538,510],{}," as a zero struct with ",[14,541,542],{},"Valid: false"," —\nthe same statement, without the claim; a Python client gets the ",[14,545,546],{},"KeyError","\nthis protocol considers the more honest error.",[19,549,550,553,554,557,558,561],{},[14,551,552],{},"FixType"," and the three ",[14,555,556],{},"VendorID"," fields are absent: the contract payload has\nno field for any of them. The same omission rule as for ",[14,559,560],{},"Raim",".",[19,563,564,567,568,570,571,577,578,561],{},[14,565,566],{},"Dimension"," is a different case since the contract gained vessel dimensions:\nthe data now exists, and ",[14,569,16],{}," still does not send it. That is a decision, not\na limitation — this endpoint exists for the migration, and vessel dimensions\nare reachable on ",[572,573,575],"a",{"href":574},"\u002Fen\u002Fdocs\u002Fstream\u002Fprotocol",[14,576,39],{}," in ",[14,579,580],{},"fields:\"full\"",[19,582,583,584,587,588,590],{},"A filter that names ",[26,585,586],{},"exclusively"," undeliverable types is rejected. That\nis the only feedback ",[14,589,16],{}," has — and it also prevents a data leak: an empty\ntype mask means \"all\" in the filter, so \"AtoN only\" would otherwise become\n\"everything\".",[19,592,593,596,597,599,600,144,602,604,605,504,607,144,609,611],{},[14,594,595],{},"receiver"," does not apply on ",[14,598,16],{}," (see difference 9), so only feed data\narrives. Which frames come out of that depends on the feed: some feeds label\nevery position type 1 and every static message type 5, so from them you get\nonly ",[14,601,412],{},[14,603,427],{},". Others pass the real type\nthrough, so from them you also get ",[14,606,441],{},[14,608,455],{},[14,610,469],{}," — in Norwegian waters\ntype 24 is the most common static message, and for Class B vessels it is the\nonly route to a name.",[19,613,614,619,620,623,624,627],{},[26,615,616,618],{},[14,617,427],{}," and bounding boxes are mutually exclusive."," A static\nmessage carries no position in the contract, and a message without a\nposition cannot satisfy a box. A subscription ",[26,621,622],{},"without"," ",[14,625,626],{},"BoundingBoxes","\nreceives static messages; as soon as a box is set, it receives position\nreports only. The original solved this from its own vessel state — it knew\nwhere a ship last stood and attributed its static message to the box. This\ngateway holds no vessel state (see difference 1) and cannot make that\nattribution.",[50,629,631],{"id":630},"when-a-limit-is-hit","When a limit is hit",[19,633,634,636,637,640,641,644,645,647],{},[14,635,16],{}," has the same response for every limit, because there is no other: an\n",[14,638,639],{},"{\"error\":\"…\"}"," with explanatory text, then ",[26,642,643],{},"close 1000",". On ",[14,646,39],{}," each\none has its own 4xxx code that a client can evaluate.",[82,649,650,665],{},[85,651,652],{},[88,653,654,657,661],{},[91,655,656],{},"Reason",[91,658,659],{},[14,660,39],{},[91,662,663],{},[14,664,16],{},[99,666,667,688,704,721,743,764,782],{},[88,668,669,672,679],{},[104,670,671],{},"Monthly volume exhausted at connect",[104,673,674,675,678],{},"HTTP ",[26,676,677],{},"403"," before the upgrade",[104,680,681,683,684,687],{},[14,682,279],{}," + close 1000 ",[26,685,686],{},"after"," the upgrade — the key is not known before that",[88,689,690,693,699],{},[104,691,692],{},"Monthly volume exhausted during the session",[104,694,695,696],{},"Close ",[26,697,698],{},"4010",[104,700,701,703],{},[14,702,279],{}," + close 1000",[88,705,706,709,717],{},[104,707,708],{},"Client reads too slowly (> 1000 dropped frames in 60 s)",[104,710,711,713,714],{},[14,712,279],{}," + close ",[26,715,716],{},"4008",[104,718,719,703],{},[14,720,279],{},[88,722,723,726,735],{},[104,724,725],{},"Output rate exceeded continuously for 60 s",[104,727,728,731,732],{},[14,729,730],{},"notice{throttled}"," ongoing, then close ",[26,733,734],{},"4009",[104,736,737,739,740,742],{},[14,738,279],{}," + close 1000, ",[26,741,622],{}," an ongoing notice",[88,744,745,748,757],{},[104,746,747],{},"Key revoked or scope withdrawn",[104,749,695,750,753,754],{},[26,751,752],{},"4011"," \u002F ",[26,755,756],{},"4002",[104,758,759,761,762,703],{},[14,760,279],{}," with the wording ",[14,763,295],{},[88,765,766,769,777],{},[104,767,768],{},"Service shuts down",[104,770,771,713,773,776],{},[14,772,279],{},[26,774,775],{},"4012",", staggered",[104,778,779,781],{},[14,780,279],{}," with the same reason text + close 1000, staggered",[88,783,784,787,792],{},[104,785,786],{},"Five frames too fast in a row",[104,788,695,789],{},[26,790,791],{},"4006",[104,793,794,703],{},[14,795,279],{},[19,797,798,623,804,806,807,809,810,813,814,817],{},[26,799,800,801,803],{},"Not every ",[14,802,279],{}," frame is fatal.",[14,805,16],{}," has exactly one frame type for\neverything that is not data, so the same ",[14,808,639],{}," also carries\nadvisories. Three of them leave the connection standing: a subscription that\nmatched nothing in the recent traffic (the coverage notice, difference 10), a\nframe that arrived less than a second after the previous one, and the ",[26,811,812],{},"first","\nunreadable or invalid frame. The connection ends on the second protocol error\nin a row, on the fifth too-fast frame in a row, and on the limits in the table\nabove. A client that treats every ",[14,815,816],{},"{\"error\":…}"," as terminal and closes by\nitself throws away a healthy connection on the most common of the three — the\ncoverage notice, which arrives right after a subscribe whose box has no traffic\nin it.",[19,819,820,623,823,825,826,830,831,833,834,561],{},[26,821,822],{},"Why no ongoing throttling notice?",[14,824,16],{}," has neither a heartbeat that\ncould carry a loss counter, nor a frame type an aisstream client is sure to\nread as ",[827,828,829],"em",{},"not"," fatal — the three advisories above run exactly that risk, which\nis why there are three of them and not a running commentary. Loss below the\nthreshold therefore stays unnamed on ",[14,832,16],{}," — the backlog still becomes visible,\nbecause the connection names it at the end instead of letting it run on\nsilently (difference 11). Anyone who wants to see the losses continuously takes\n",[14,835,39],{},[19,837,838,841,842,844,845,847,848,851],{},[26,839,840],{},"There is no welcome frame."," The first frame a ",[14,843,16],{}," client sees is a\nmessage or an error. On ",[14,846,39],{}," the ",[14,849,850],{},"welcome"," carries the limits, the sources and\ntheir licence texts. Here the limits are in this document; the attribution\ntexts are in the Terms of Use and on the website (difference 9).",[50,853,855],{"id":854},"data-frame-fields","Data frame fields",[19,857,858,859,862,863,866],{},"What is sent is what the ",[14,860,861],{},"AisPosition v1"," contract carries. Everything\nelse ",[26,864,865],{},"is missing"," — it does not appear with a null value, it does not\nappear at all.",[868,869,871,873],"h3",{"id":870},"positionreport-type-1-2-3",[14,872,412],{}," (type 1, 2, 3)",[82,875,876,886],{},[85,877,878],{},[88,879,880,883],{},[91,881,882],{},"Field",[91,884,885],{},"Source",[99,887,888,905,928,940,955,969,983,999,1013,1025],{},[88,889,890,895],{},[104,891,892],{},[14,893,894],{},"Cog",[104,896,897,485,900],{},[14,898,899],{},"cog",[26,901,902,903],{},"0 if ",[14,904,47],{},[88,906,907,915],{},[104,908,909,485,912],{},[14,910,911],{},"Latitude",[14,913,914],{},"Longitude",[104,916,917,485,920,923,924,927],{},[14,918,919],{},"lat",[14,921,922],{},"lon"," as ",[26,925,926],{},"raw JSON values","; missing without a usable position",[88,929,930,935],{},[104,931,932],{},[14,933,934],{},"MessageID",[104,936,937],{},[14,938,939],{},"msg_type",[88,941,942,947],{},[104,943,944],{},[14,945,946],{},"NavigationalStatus",[104,948,949,952,953],{},[14,950,951],{},"nav_status",", missing when ",[14,954,47],{},[88,956,957,962],{},[104,958,959],{},[14,960,961],{},"PositionAccuracy",[104,963,964,952,967],{},[14,965,966],{},"pos_accuracy",[14,968,47],{},[88,970,971,976],{},[104,972,973],{},[14,974,975],{},"RateOfTurn",[104,977,978,952,981],{},[14,979,980],{},"rot",[14,982,47],{},[88,984,985,990],{},[104,986,987],{},[14,988,989],{},"Sog",[104,991,992,485,995],{},[14,993,994],{},"sog",[26,996,902,997],{},[14,998,47],{},[88,1000,1001,1006],{},[104,1002,1003],{},[14,1004,1005],{},"TrueHeading",[104,1007,1008,952,1011],{},[14,1009,1010],{},"heading",[14,1012,47],{},[88,1014,1015,1020],{},[104,1016,1017],{},[14,1018,1019],{},"UserID",[104,1021,1022],{},[14,1023,1024],{},"mmsi",[88,1026,1027,1032],{},[104,1028,1029],{},[14,1030,1031],{},"Valid",[104,1033,1034,1035],{},"always ",[14,1036,1037],{},"true",[19,1039,1040,1042,1043,1045,144,1047,1049,1050,1052,1053,144,1056,1059],{},[14,1041,441],{}," (18) carries the same fields ",[26,1044,622],{},[14,1046,946],{},[14,1048,975],{}," — the original's Class B frame does\nnot have them. ",[14,1051,455],{}," (19) additionally carries\n",[14,1054,1055],{},"Name",[14,1057,1058],{},"Type",", because type 19 carries them in the radio message\nitself.",[868,1061,1063,1065],{"id":1062},"shipstaticdata-type-5",[14,1064,427],{}," (type 5)",[82,1067,1068,1076],{},[85,1069,1070],{},[88,1071,1072,1074],{},[91,1073,882],{},[91,1075,885],{},[99,1077,1078,1090,1102,1118,1130,1143,1153,1164,1175,1185],{},[88,1079,1080,1085],{},[104,1081,1082],{},[14,1083,1084],{},"CallSign",[104,1086,1087],{},[14,1088,1089],{},"callsign",[88,1091,1092,1097],{},[104,1093,1094],{},[14,1095,1096],{},"Destination",[104,1098,1099],{},[14,1100,1101],{},"destination",[88,1103,1104,1109],{},[104,1105,1106],{},[14,1107,1108],{},"Eta",[104,1110,1111,1114,1115],{},[14,1112,1113],{},"eta",", converted to ",[14,1116,1117],{},"{\"Month\":…,\"Day\":…,\"Hour\":…,\"Minute\":…}",[88,1119,1120,1125],{},[104,1121,1122],{},[14,1123,1124],{},"ImoNumber",[104,1126,1127],{},[14,1128,1129],{},"imo",[88,1131,1132,1137],{},[104,1133,1134],{},[14,1135,1136],{},"MaximumStaticDraught",[104,1138,1139,1142],{},[14,1140,1141],{},"draught"," (metres, not decimetres)",[88,1144,1145,1149],{},[104,1146,1147],{},[14,1148,934],{},[104,1150,1151],{},[14,1152,939],{},[88,1154,1155,1159],{},[104,1156,1157],{},[14,1158,1055],{},[104,1160,1161],{},[14,1162,1163],{},"shipname",[88,1165,1166,1170],{},[104,1167,1168],{},[14,1169,1058],{},[104,1171,1172],{},[14,1173,1174],{},"shiptype",[88,1176,1177,1181],{},[104,1178,1179],{},[14,1180,1019],{},[104,1182,1183],{},[14,1184,1024],{},[88,1186,1187,1191],{},[104,1188,1189],{},[14,1190,1031],{},[104,1192,1034,1193],{},[14,1194,1037],{},[19,1196,1197,1198,1201,1202,1205],{},"The contract carries the ETA as ",[14,1199,1200],{},"\"MM-DDTHH:MM\""," — lossless, because AIS\ndoes not transmit a year. The conversion into the four numbers is\nunambiguous; ",[26,1203,1204],{},"adding a year would be an interpretation"," and is left out.\nAn ETA that does not match the contract form is omitted, not guessed.",[868,1207,1209],{"id":1208},"metadata",[14,1210,32],{},[82,1212,1213,1222],{},[85,1214,1215],{},[88,1216,1217,1219],{},[91,1218,882],{},[91,1220,1221],{},"Value",[99,1223,1224,1235,1250,1268],{},[88,1225,1226,1231],{},[104,1227,1228],{},[14,1229,1230],{},"MMSI",[104,1232,1233],{},[14,1234,1024],{},[88,1236,1237,1242],{},[104,1238,1239],{},[14,1240,1241],{},"ShipName",[104,1243,1244,1249],{},[26,1245,1034,1246],{},[14,1247,1248],{},"\"\""," — see difference 1",[88,1251,1252,1260],{},[104,1253,1254,485,1257],{},[14,1255,1256],{},"latitude",[14,1258,1259],{},"longitude",[104,1261,1262,1263,1265,1266,927],{},"raw ",[14,1264,919],{},"\u002F",[14,1267,922],{},[88,1269,1270,1275],{},[104,1271,1272],{},[14,1273,1274],{},"time_utc",[104,1276,1277,1280,1281],{},[14,1278,1279],{},"ingest_ts"," in the format ",[14,1282,1283],{},"2026-08-13 10:00:00.500000 +0000 UTC",[19,1285,1286],{},"The mixed case is the original's and intentional.",[19,1288,1289,1291,1292,1295,1296,1299,1300,1303,1304,1307,1308,1311,1312,1314],{},[14,1290,1274],{}," carries Go's ",[14,1293,1294],{},"time.String()"," form, but ",[26,1297,1298],{},"forced to six decimal\nplaces",": Go's ",[14,1301,1302],{},"String()"," trims trailing zeros, and a client with a fixed\nfield width would break on ",[14,1305,1306],{},".5 +0000 UTC"," instead of ",[14,1309,1310],{},".500000 +0000 UTC",".\nIt is truncated, not rounded. An unreadable ",[14,1313,1279],{}," leaves the field\nout, rather than claiming year 1.",[19,1316,1317,1318,1321,1322,1325],{},"We ",[26,1319,1320],{},"do not"," send ",[14,1323,1324],{},"MMSI_String",": in archived examples it holds a number\ndespite the name, and which type was meant cannot be determined without a\nreference server. A guessed type would be worse than a missing field — the\nsame rule as in difference 3 applies.",[50,1327,1329],{"id":1328},"after-an-upstream-outage-the-gap-stays-a-gap","After an upstream outage, the gap stays a gap",[19,1331,1332],{},"This is not a difference from the original protocol but a property of the\nservice, so it stands ahead of the numbered list.",[19,1334,1335,1336,1339,1340,1342],{},"If our upstream is unreachable for more than a minute, we ",[26,1337,1338],{},"discard"," the\nbacklog rather than replaying it at you as live data. You will see a gap in\nthe stream, and ",[14,1341,1274],{}," on the frames after it jumps forward.",[19,1344,1345],{},"This is deliberate. A replayed backlog arriving at full speed looks like\nlive traffic, puts every vessel at a position it left minutes ago, and is\nindistinguishable from the real thing at the client. A gap is honest and\nrecoverable — the next frame for each vessel carries its current position.",[50,1347,1349],{"id":1348},"the-eleven-differences","The eleven differences",[868,1351,1353,1354,1357],{"id":1352},"_1-metadatashipname-is-always-empty","1. ",[14,1355,1356],{},"MetaData.ShipName"," is always empty",[19,1359,1360,1361,561],{},"The original fills the name from its own vessel state; a position report\ncarries none. This gateway holds no vessel state and delivers ",[14,1362,1248],{},[19,1364,1365,1368,1369,1371],{},[26,1366,1367],{},"The most serious single difference",", because many clients key off\n",[14,1370,1241],{},". Even a static message that does carry the name does not fill\nit: a field that is populated in a small minority of frames and empty in the\nrest looks to the client like a working field with gaps — and that is harder\nto spot than one that is empty throughout.",[19,1373,1374,1375,1378,1379,1381,1382,561],{},"The name is in the static message (",[14,1376,1377],{},"ShipStaticData.Name",") and on ",[14,1380,39],{}," in\nthe field ",[14,1383,1163],{},[868,1385,1387],{"id":1386},"_2-we-never-deliver-20-of-the-originals-25-types","2. We never deliver 20 of the original's 25 types",[19,1389,1390,1392,1393,1396,1397,1400],{},[14,1391,861],{}," is a flat union of scalar fields ",[26,1394,1395],{},"without type-specific raw\nbit fields",". What cannot be derived from it does not exist. The type\ntable above is complete; a type that cannot be mapped means ",[26,1398,1399],{},"no frame at\nall"," is sent — better none than an invented one.",[868,1402,1404],{"id":1403},"_3-missing-fields-are-omitted-not-filled-with-zero-values","3. Missing fields are omitted, not filled with zero values",[19,1406,1407,1408,485,1411,504,1414,485,1417,485,1420,485,1422,504,1425,485,1428,485,1431,1433,1434,561],{},"We do not have: ",[14,1409,1410],{},"RepeatIndicator",[14,1412,1413],{},"Timestamp",[14,1415,1416],{},"SpecialManoeuvreIndicator",[14,1418,1419],{},"Spare",[14,1421,560],{},[14,1423,1424],{},"CommunicationState",[14,1426,1427],{},"AisVersion",[14,1429,1430],{},"Dte",[14,1432,552],{}," — and ",[14,1435,1324],{},[19,1437,1438,1441,1442,1444],{},[14,1439,1440],{},"Dimension{A,B,C,D}"," is not in that list any more: the contract carries the\nfour offsets. ",[14,1443,16],{}," does not send them, deliberately — see difference 1.",[19,1446,1447,1448,1451,1452],{},"Sending ",[14,1449,1450],{},"\"Raim\":false"," would be a claim about the radio link that we never\nmade. For a maritime data product with the disclaimer \"not a navigation\naid\", that is the one mistake you must not make. ",[26,1453,1454,1455,1457],{},"The client's ",[14,1456,546],{},"\nis the more honest error message.",[19,1459,1460,1461,1463,1464,485,1466,485,1468,485,1470,1472,1473,1481],{},"The same applies to contract fields that are ",[14,1462,47],{}," in an individual case:\n",[14,1465,946],{},[14,1467,975],{},[14,1469,961],{},[14,1471,1005],{}," and\nthe coordinates are then missing, rather than claiming a 0. ",[26,1474,1475,1476,1478,1479],{},"No ",[14,1477,16],{},"\nframe ever contains a ",[14,1480,47],{}," — the original's format does not have one.",[19,1483,1484,1485,1488,1489,1491,1492,561],{},"Anyone who needs a fixed field list takes the ",[14,1486,1487],{},"position"," profile on ",[14,1490,39],{},":\nall eight fields are always present there, and a missing value is\nexplicitly ",[14,1493,47],{},[868,1495,1497,1498,1500,1501],{"id":1496},"_4-valid-is-always-true","4. ",[14,1499,1031],{}," is always ",[14,1502,1037],{},[19,1504,1505,1506,1509],{},"For collector data the authority's decoder made this decision, not ours.\nWe would have no source of our own for a validity claim; sending ",[14,1507,1508],{},"false","\nwould be a claim about someone else's work.",[868,1511,1513,1514,1516],{"id":1512},"_5-time_utc-means-something-else","5. ",[14,1515,1274],{}," means something else",[19,1518,1519,1520,1523,1524,1526],{},"For the original it is the receive time at ",[26,1521,1522],{},"their"," server; for us it is\n",[14,1525,1279],{}," — the moment this platform accepted the message. The\ndifference is the transit time through the feed and is typically seconds.",[19,1528,1529,1530,1532,1533,1536,1537,1539],{},"On ",[14,1531,39],{}," both stand cleanly side by side: ",[14,1534,1535],{},"rx_ts"," is the receive time at\nthe antenna, ",[14,1538,1279],{}," the acceptance.",[868,1541,1543,1544,1265,1546,1548],{"id":1542},"_6-the-sogcog-sentinels-are-lost","6. The ",[14,1545,989],{},[14,1547,894],{}," sentinels are lost",[19,1550,1551,1552,1554,1555,1557,1558,1560,1561,1564,1565,561],{},"AIS encodes \"not available\" as 102.3 kn and 360 degrees respectively. Our\ncontract turns that into ",[14,1553,47],{},", and ",[14,1556,16],{}," has no ",[14,1559,47],{}," in a ",[14,1562,1563],{},"float"," —\n",[26,1566,1567,1568],{},"we send ",[14,1569,532],{},[19,1571,1572,1578,1579,1581,1582,644,1584,485,1586,1588],{},[26,1573,1574,1575,1577],{},"A ",[14,1576,16],{}," client can no longer distinguish \"not available\" from \"lying\nstill\"."," This is the only place where ",[14,1580,16],{}," invents a value instead of\nomitting it; unavoidable, because the original's field manages without\n",[14,1583,47],{},[14,1585,39],{},[14,1587,47],{}," is preserved.",[868,1590,1592,1593,1595],{"id":1591},"_7-msg_type-from-the-collector-source-is-assigned-not-original","7. ",[14,1594,939],{}," from the collector source is assigned, not original",[19,1597,1598,1599,1602,1603,1605,1606,561],{},"Some collector feeds do not pass through the original AIS type. Such a feed\ntherefore carries an ",[26,1600,1601],{},"assigned"," type: 1 for a position report, 5 for\nmetadata. A Class B position report thus appears as ",[14,1604,412],{}," (type\n1), not as ",[14,1607,441],{},[19,1609,1610,1611,1614],{},"This also affects the filter:\n",[14,1612,1613],{},"FilterMessageTypes: [\"StandardClassBPositionReport\"]"," delivers nothing\nfrom a feed like that.",[868,1616,1618],{"id":1617},"_8-the-antimeridian-cannot-be-expressed","8. The antimeridian cannot be expressed",[19,1620,1621,1622,1625,1626,1629,1630,188,1633,1636],{},"The original normalises a box's corners with min\u002Fmax, and so do we. ",[14,1623,1624],{},"170","\nand ",[14,1627,1628],{},"-175"," therefore become ",[14,1631,1632],{},"-175..170",[26,1634,1635],{},"half the world instead of a\nnarrow strip"," across the date line.",[19,1638,1639,1641,1642,1645],{},[14,1640,39],{}," can express this case: there, ",[14,1643,1644],{},"west > east"," explicitly means \"across\n180 degrees\".",[868,1647,1649],{"id":1648},"_9-attribution-cannot-travel-with-the-data","9. Attribution cannot travel with the data",[19,1651,1652,1653,1655],{},"The original's frame has no licence field. On ",[14,1654,16],{},", attribution therefore\ncannot travel with each individual message.",[19,1657,1658,1660,1661,1667,1668,1670,1671,1674,1675,1677,1678],{},[14,1659,16],{}," therefore delivers ",[26,1662,1663,1666],{},[14,1664,1665],{},"collector"," data exclusively",", and attribution\nis in the Terms of Use and on the website instead of in the frame. Anyone\nwho needs the licence per message takes ",[14,1669,39],{}," — there every data frame\ncarries a ",[14,1672,1673],{},"license"," field, and the ",[14,1676,850],{}," frame maps that identifier to\nits attribution text in machine-readable form. ",[26,1679,1680],{},"A real gap, and it is\nstated as one here.",[868,1682,1684],{"id":1683},"_10-coverage-differs-and-it-is-no-longer-a-nordic-only-feed","10. Coverage differs, and it is no longer a Nordic-only feed",[19,1686,1687,1688,1691],{},"aisstream.io was worldwide. So, today, is a large part of what we carry: the\nglobal share is ",[26,1689,1690],{},"sampled",", at roughly one position per vessel per minute.\nThe Nordic feeds are continuous but regional. What differs is not the map but\nthe update rate.",[19,1693,1694,1695,1697,1700,1701,1703,1704,1707,1708,1710],{},"The other half of the same subject: where a subscription matches nothing, ",[14,1696,16],{},[26,1698,1699],{},"says so",", and the original would have stayed silent. After a subscribe whose\nfilter matched none of the recent traffic, this endpoint sends a single\n",[14,1702,639],{}," whose text says exactly that — and ",[26,1705,1706],{},"keeps the connection\nopen",". It is the one advisory ",[14,1709,16],{}," has, it is not fatal, and it exists\nbecause silence, not an error, is the most common disappointment when moving\nover.",[868,1712,1714,1715],{"id":1713},"_11-not-reproduced-silent-throttling-and-metadata","11. Not reproduced: silent throttling and ",[14,1716,1717],{},"Metadata",[19,1719,1720,1721,1724,1725,1727,1728,1731,1732,1734,1735,1737],{},"The original throttled a slow client ",[26,1722,1723],{},"silently",". We do not — not all the way\nto the end, at least: on ",[14,1726,16],{}," the backlog becomes visible when the connection\nends with an error frame that names it, instead of the client never learning at\nall. Losses ",[26,1729,1730],{},"below"," the threshold do stay unnamed on ",[14,1733,16],{},", as above: there\nis no frame type that could report them without an aisstream client reading it\nas fatal. On ",[14,1736,39],{}," the heartbeat names them continuously.",[19,1739,1740,1741,1746,1747,1749],{},"We also send ",[26,1742,1743,1744],{},"only ",[14,1745,32],{}," — the spelling ",[14,1748,1717],{}," that also\nappears in some archived examples does not exist here.",[50,1751,1753,1754,1756],{"id":1752},"where-v0-is-more-lenient-than-the-original","Where ",[14,1755,16],{}," is more lenient than the original",[19,1758,1759,1760,1763],{},"These points are not differences in the sense above: they accept ",[26,1761,1762],{},"more","\nthan the original assumed, and cannot break a running client.",[1765,1766,1767,1779,1787,1800],"ul",{},[1768,1769,1770,1773,1774,485,1776,1778],"li",{},[26,1771,1772],{},"Both spellings"," of the key field (",[14,1775,143],{},[14,1777,147],{},").",[1768,1780,1781,1786],{},[26,1782,1783,1784],{},"Missing or empty ",[14,1785,626],{}," mean \"worldwide\" instead of a\nrejection.",[1768,1788,1789,1790,577,1792,485,1794,1796,1797,1799],{},"An explicit ",[14,1791,47],{},[14,1793,626],{},[14,1795,209],{}," or\n",[14,1798,228],{}," counts as a missing field, i.e. \"no filter\".",[1768,1801,1802,1805],{},[26,1803,1804],{},"Unknown fields"," in the subscribe frame are ignored.",[50,1807,1809],{"id":1808},"the-key-lives-in-the-message-body","The key lives in the message body",[19,1811,1529,1812,1814,1815,1817,1818,1820,1821,1823,1824,561],{},[14,1813,16],{}," the subscribe frame carries the ",[14,1816,143],{}," — unlike ",[14,1819,39],{},", where it\nlives in the ",[14,1822,156],{}," header. A full-text log of a rejected\nsubscribe would therefore be ",[26,1825,1826],{},"a plaintext leak by design",[19,1828,1829,1830,1833,1834,1837],{},"The debug mode that logs rejected frames therefore goes exclusively\nthrough a masking step. It replaces the value of every\nkey-like field — including in nested objects — with ",[14,1831,1832],{},"\"***\"",", and for an\n",[26,1835,1836],{},"unreadable"," frame it does not echo it at all, only its length. There is\nno fallback to \"log it raw instead\": unreadable JSON is the most common\nrejected frame, and that is exactly where the leak would sit.",[50,1839,1841],{"id":1840},"limits","Limits",[82,1843,1844,1859],{},[85,1845,1846],{},[88,1847,1848,1851,1855],{},[91,1849,1850],{},"Limit",[91,1852,1853],{},[14,1854,16],{},[91,1856,1857],{},[14,1858,39],{},[99,1860,1861,1869,1880,1890,1901,1911],{},[88,1862,1863,1865,1867],{},[104,1864,197],{},[104,1866,202],{},[104,1868,202],{},[88,1870,1871,1874,1877],{},[104,1872,1873],{},"Bounding boxes per connection",[104,1875,1876],{},"50 (one subscription)",[104,1878,1879],{},"80 (across all subscriptions)",[88,1881,1882,1885,1887],{},[104,1883,1884],{},"MMSI per subscription",[104,1886,202],{},[104,1888,1889],{},"500",[88,1891,1892,1895,1898],{},[104,1893,1894],{},"Subscriptions per connection",[104,1896,1897],{},"1 (each frame replaces everything)",[104,1899,1900],{},"4, individually replaceable",[88,1902,1903,1906,1908],{},[104,1904,1905],{},"Deadline for the first frame",[104,1907,130],{},[104,1909,1910],{},"5 s",[88,1912,1913,1916,1919],{},[104,1914,1915],{},"Minimum gap between two subscribes",[104,1917,1918],{},"1 s",[104,1920,1910],{},[19,1922,1923,1924,1926,1927,1929,1930,1625,1933,1936,1937,1939,1940,1943,1944,1946,1947,1949,1950,1952],{},"All remaining limits apply equally to both endpoints: connections per\naccount, output rate and monthly volume. The values in force for a given key\nare named on ",[14,1925,39],{}," by the ",[14,1928,850],{}," frame in ",[14,1931,1932],{},"limits.output_bytes_per_second",[14,1934,1935],{},"limits.quota_bytes_per_month","; a ",[14,1938,532],{}," there explicitly means ",[827,1941,1942],{},"no limit",".\n",[14,1945,16],{}," has no welcome frame and therefore cannot announce them — if you need\nto know, connect once on ",[14,1948,39],{}," and read them there. The two rows about them\nin the error table above describe what happens on ",[14,1951,16],{}," once one of the\nlimits bites.",[1954,1955,1956],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":313,"searchDepth":1958,"depth":1958,"links":1959},2,[1960,1962,1963,1964,1965,1973,1974,1993,1995,1996],{"id":52,"depth":1958,"text":1961},"The most important thing first: \u002Fv0 cannot be verified",{"id":79,"depth":1958,"text":80},{"id":364,"depth":1958,"text":365},{"id":630,"depth":1958,"text":631},{"id":854,"depth":1958,"text":855,"children":1966},[1967,1970,1972],{"id":870,"depth":1968,"text":1969},3,"PositionReport (type 1, 2, 3)",{"id":1062,"depth":1968,"text":1971},"ShipStaticData (type 5)",{"id":1208,"depth":1968,"text":32},{"id":1328,"depth":1958,"text":1329},{"id":1348,"depth":1958,"text":1349,"children":1975},[1976,1978,1979,1980,1982,1984,1986,1988,1989,1990,1991],{"id":1352,"depth":1968,"text":1977},"1. MetaData.ShipName is always empty",{"id":1386,"depth":1968,"text":1387},{"id":1403,"depth":1968,"text":1404},{"id":1496,"depth":1968,"text":1981},"4. Valid is always true",{"id":1512,"depth":1968,"text":1983},"5. time_utc means something else",{"id":1542,"depth":1968,"text":1985},"6. The Sog\u002FCog sentinels are lost",{"id":1591,"depth":1968,"text":1987},"7. msg_type from the collector source is assigned, not original",{"id":1617,"depth":1968,"text":1618},{"id":1648,"depth":1968,"text":1649},{"id":1683,"depth":1968,"text":1684},{"id":1713,"depth":1968,"text":1992},"11. Not reproduced: silent throttling and Metadata",{"id":1752,"depth":1958,"text":1994},"Where \u002Fv0 is more lenient than the original",{"id":1808,"depth":1958,"text":1809},{"id":1840,"depth":1958,"text":1841},"Moving an existing aisstream.io client to Pelyr OPEN-AIS.","md",{},true,"\u002Fdocs\u002Fstream\u002Faisstream",{"title":5,"description":1997},"docs\u002Fstream\u002Faisstream","Ak1BoLZN3uBu1iLdcBugK8RFiH3IuBH8el9JzBguXv0",1789034411683]