/api/v1/archive
What Eternaltwin has kept of the games it preserves: Dinoparc, Hammerfest and
Twinoid. Defined in crates/rest/src/archive/.
Every route here is a public read. The handlers build a guest context and ignore whatever credentials the request carried, so an access token buys nothing and a session buys nothing: the archive is the same for everybody.
A read never scrapes on demand; it answers from the store. A profile nobody ever archived is a 404, not an empty object.
Shape of an archived record
Archived values are foreign data: Eternaltwin does not own them, it observed them. Each one is wrapped in a snapshot that says when it was seen:
{
"latest": {
"period": {"start": "2021-01-15T14:17:14.015Z", "end": null},
"retrieved": {"latest": "2021-01-15T14:17:14.015Z"},
"value": 42
}
}
period.start is when the value first read this way, period.end is when it
stopped (null while it still holds), and retrieved.latest is the last time
the value was actually confirmed. A field that was never archived is null
rather than an empty snapshot.
The records of a game account also carry an etwin block: the Eternaltwin
accounts this remote account is or was linked to, as {"current": …, "old": []}. It is the mirror image of the links field of a
user.
Errors
The archive routes answer with a tagged error rather than the usual
{"error": "<message>"} object. The body is {"error": "<Variant>"} where
the variant is one of the names below.
| Status | Body | When |
|---|---|---|
| 404 | {"error": "DinoparcUserNotFound"} etc. | Nothing archived under that id. |
| 500 | {"error": "InternalServerError"} | — |
Dinoparc
:server is one of dinoparc.com, en.dinoparc.com, sp.dinoparc.com.
Identifiers are decimal strings.
GET /dinoparc/:server/users/:user_id
Returns the archived Dinoparc player.
GET /api/v1/archive/dinoparc/dinoparc.com/users/205944
{
"type": "DinoparcUser",
"server": "dinoparc.com",
"id": "205944",
"archived_at": "2021-01-15T14:17:14.015Z",
"username": "djlue",
"coins": {
"latest": {
"period": {"start": "2021-01-15T14:17:14.015Z", "end": null},
"retrieved": {"latest": "2021-01-15T14:17:14.015Z"},
"value": 10000
}
},
"dinoz": null,
"inventory": null,
"collection": null,
"etwin": {"current": null, "old": []}
}
inventory maps item identifiers to counts; collection holds the epic
rewards and collected items.
Errors: 404DinoparcUserNotFound, 500InternalServerError.
GET /dinoparc/:server/dinoz/:user_id
Returns an archived dinoz. Despite the parameter's name in the router, the value is a dinoz identifier.
GET /api/v1/archive/dinoparc/dinoparc.com/dinoz/2568082
{
"type": "DinoparcDinoz",
"server": "dinoparc.com",
"id": "2568082",
"archived_at": "2021-01-15T14:17:14.015Z",
"name": null,
"owner": null,
"location": null,
"race": null,
"skin": null,
"life": null,
"level": null,
"experience": null,
"danger": null,
"in_tournament": null,
"elements": null,
"skills": null
}
Every field but server, id and archived_at is a snapshot or null: a
dinoz seen only in a listing has a name and nothing else. life and
experience are integer percentages, skills maps a skill name to a level in
0..=5, and there is no etwin block — a dinoz belongs to a Dinoparc player,
and it is that player who carries the link.
Errors: 404DinoparcDinozNotFound, 500InternalServerError.
Hammerfest
:server is one of hammerfest.fr, hfest.net, hammerfest.es.
GET /hammerfest/:server/users/:user_id
GET /api/v1/archive/hammerfest/hammerfest.fr/users/127
{
"type": "HammerfestUser",
"server": "hammerfest.fr",
"id": "127",
"username": "elseabora",
"archived_at": "2021-01-15T14:17:14.015Z",
"profile": {
"latest": {
"period": {"start": "2021-01-15T14:17:14.015Z", "end": null},
"retrieved": {"latest": "2021-01-15T14:17:14.015Z"},
"value": {
"best_score": 0,
"best_level": 0,
"game_completed": false,
"items": [],
"quests": {}
}
}
},
"inventory": null,
"etwin": {"current": null, "old": []}
}
quests maps a quest identifier to "None", "Pending" or "Complete";
items is the set of item identifiers shown on the profile. inventory is
the counted inventory, which is only known for an account whose session was
archived.
Errors: 404HammerfestUserNotFound, 500InternalServerError.
Twinoid
Twinoid has no per-server split: one namespace, decimal identifiers.
GET /twinoid/users/:user_id
GET /api/v1/archive/twinoid/users/38
{
"type": "TwinoidUser",
"id": "38",
"archived_at": "2021-01-15T14:17:14.015Z",
"display_name": "Alice",
"links": [],
"etwin": {"current": null, "old": []}
}
links are the Twinoid site links — the games a Twinoid account is
connected to. Each entry pairs a site object, which carries id and host,
with a user snapshot naming the account's identifier on that site:
{
"site": {"type": "TwinoidSite", "id": 44, "host": "hammerfest.fr"},
"user": {
"latest": {
"period": {"start": "2021-01-15T14:17:14.015Z", "end": null},
"retrieved": {"latest": "2021-01-15T14:17:14.015Z"},
"value": {"id": 127}
}
}
}
| Status | Body | When |
|---|---|---|
| 404 | {"error": "TwinoidUserNotFound"} | Nothing archived under that id. |
| 404 | {"error": "GetEtwinLinkedBy"} | The link exists but the user who made it could not be read. |
| 404 | {"error": "GetLinkedEtwin"} | The link exists but the linked Eternaltwin account could not be read. |
| 500 | {"error": "InternalServerError"} | — |
GET /twinoid/users/:user_id/scores
The Twinoid points this account holds on each site it plays.
[
{
"site": {
"type": "TwinoidSite",
"id": 44,
"lang": [{"locale": "fr", "description": "Hammerfest"}],
"name": "Hammerfest"
},
"points": 1250.0
}
]
An array, not a listing: the number of sites is small and fixed. An account
with no archived score answers [].
Errors: 500InternalServerError.
GET /twinoid/users/:user_id/rewards/:site_id
The statistics and achievements of one account on one site. :site_id is the
integer Twinoid site identifier.
GET /api/v1/archive/twinoid/users/38/rewards/44
{
"stats": [
{"stat_key": "score", "score": 1250, "rarity": 12}
],
"rewards": [
{"achievement_key": "first_win", "points": 5.0}
]
}
Errors: 500InternalServerError.