Eternaltwin

Home | /api | v1

/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.

StatusBodyWhen
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}
    }
  }
}
StatusBodyWhen
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.