This document describes the Client-to-Server API of Vervis. If you're developing a frontend application, or a forge search engine, or anything else that wants to interact with Vervis instances, and wondering what language Vervis speaks, this is the document for you. I suppose in the future it should sit on a Docusaurus website or something like that. For now it's here. # Public Browsing The `/browse` page lists the public actors and resource hosted on the server. However there's currently no AP version of that page. # Server Information NodeInfo isn't implemented yet. # Registration and Authorization Creating an account on a Vervis instance allows to: 1. Create and manipulate resources (such as projects, repositories, teams) 2. View non-public information ## Register the application ### Register client Send a POST request to the `/oauth/apps` endpoint: ```sh curl -X POST \ -F 'client_name=Anvil' \ -F 'redirect_uris=urn:ietf:wg:oauth:2.0:oob' \ -F 'scopes=read' \ -F 'website=https://anvil.forgefed.org' \ -F 'repository=https://codeberg.org/Anvil/Anvil' \ https://vervis.example/oauth/apps ``` - `redirect_uris` currently supports specifying only one URI, and there are 2 options: - The special value `urn:ietf:wg:oauth:2.0:oob`, which means out-of-band: In this case, Vervis will respond with an HTML page containing a token that needs to be manually copied - An HTTPS URI: In this case, Vervis will redirect to the given URI - The only supported scope is currently `read`, and despite the misleading name, it allows all API operations The response, upon success, is a JSON object with 2 text fields: - `client_id` - `client_secret` Keep these stored for future use. ### Obtain an Application Access Token Send a POST request to the `/oauth/token` endpoint: ```sh curl -X POST \ -F 'client_id=your_client_id_here' \ -F 'client_secret=your_client_secret_here' \ -F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \ -F 'grant_type=client_credentials' \ https://vervis.example/oauth/token ``` - `redirect_uri` must be the one defined when registering the application The response is a JSON object with: - `access_token`: Text, keep for later use - `token_type`: Text, always "Bearer" - `scope`: Text, always "read" - `created_at`: Integer, time as POSIX seconds ### Verify the Application Access Token You can verify the token works by sending a GET request to the `/oauth/verify_credentials` endpoint: ```sh curl \ -H 'Authorization: Bearer our_access_token_here' \ https://vervis.example/oauth/verify_credentials ``` ## Register an Account This section isn't implemented yet. I'm about to implement it. Putting the API here for review while I'm coding. ### Check if registration is enabled Send a GET request to the `/register/enabled` endpoint. A 2xx response indicates it's enabled, otherwise it's disabled. ### Check username availability Send a GET request to the `/register/available` endpoint: ```sh curl -X GET \ -H 'Authorization: Bearer our_application_access_token_here' \ -F 'username=alice' \ https://vervis.example/register/available ``` A 2xx response indicates the username is available. ### Create a new account Send a POST request to the `/register` endpoint: ```sh curl -X POST \ -H 'Authorization: Bearer our_application_access_token_here' \ -F 'username=alice' \ -F 'passphrase=R6GQJ9HqLtRQ58' \ -F 'email=alice@email.example' \ https://vervis.example/register ``` A 2xx response indicates the account has been created. A JSON object is returned, with a boolean `email_sent` field. If true, a verification email has been sent to the specified email address. If false, it means email verification is disabled on this server, and the account is ready to be used. ### Verify account The email contains a token, which you can send via a POST request to the `/register/verify` endpoint, in order to verify and enable the account: ```sh curl -X POST \ -H 'Authorization: Bearer our_application_access_token_here' \ -F 'username=alice' \ -F 'token=pRiW8ayeuN7UBW4qAKg9qRBE0DUVCIof' \ https://vervis.example/register/verify ``` A 2xx response indicates successful verification. ## Log in as Existing User ### Obtain authorization code In a browser, send a GET request to the `/oauth/authorize` endpoint: ``` https://vervis.example/oauth/authorize ?client_id=CLIENT_ID &scope=read &redirect_uri=urn:ietf:wg:oauth:2.0:oob &response_type=code ``` - If `redirect_uri` is the special one as in the example above, the response will be an HTML page containing the authorization code. - Otherwise, Vervis will redirect to the `redirect_uri`, specifying the authorization code as a query parameter named "code": `redirect_uri?code=qDFUEaYrRK5c-HNmTCJbAzazwLRInJ7VHFat0wcMgCU` ### Obtain a User Access Token Now that we have the code, send a POST request to the `/oauth/token` endpoint (which we previously used when registering the application): ```sh curl -X POST \ -F 'client_id=your_client_id_here' \ -F 'client_secret=your_client_secret_here' \ -F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \ -F 'grant_type=authorization_code' \ -F 'code=user_authzcode_here' \ -F 'scope=read' \ https://vervis.example/oauth/token ``` The response is a JSON object with: - `access_token`: Text, keep for later use - `token_type`: Text, always "Bearer" - `scope`: Text, always "read" - `created_at`: Integer, time as POSIX seconds ### Verify the User Access Token & Obtain Actor Object You can verify the token works by sending a GET request to the `/oauth/verify_credentials` endpoint: ```sh curl \ -H 'Authorization: Bearer our_access_token_here' \ https://vervis.example/oauth/verify_credentials ``` The response is a JSON object that has a `url` field, whose value is the HTTPS URI of the user's `Person` ActivityPub JSON object. ### Perform Authorized Requests You can now use the access token via the `Authorization` header, as in the curl example above. - For GET requests, it allows to obtain non-public data - For POST requests, it allows to publish ActivityPub activities via the user outbox # Getting ActivityPub objects You can obtain an ActivityPub object by sending GET request to its `id` URI, with `Content-Type` being `application/ld+json; profile="https://www.w3.org/ns/activitystreams`. Unless you've been given such a URI, the starting points for discovering objects are: - Public browsing page (which doesn't yet have an AP representation) - The user `Person` object, whose URI can be obtained from `/oauth/verify_credentials` as described above # The Person Object ```json { "id": "https://fig.fr33domlover.site/people/vDxKn", "type": "Person", "preferredUsername": "perelev", "summary": "Cool person who makes cool stuff", "inbox": "https://fig.fr33domlover.site/people/vDxKn/inbox", "outbox": "https://fig.fr33domlover.site/people/vDxKn/outbox", "followers": "https://fig.fr33domlover.site/people/vDxKn/followers", "following": "https://fig.fr33domlover.site/people/vDxKn/following", "sshKey": [ "https://fig.fr33domlover.site/people/vDxKn/ssh-keys/Pn9Yn" ] } ``` # Receiving Messages Requests and event notifications are received as ActivityPub `Activity` objects in the Person's `inbox` collection. Currently push notifications aren't implemented, so client applications need to periodically GET the collection and detect whether new items have appeared at the top. The inbox is a (typically paged) reverse-chronologically `OrderedCollection` of `Activity` objects, as described in the ActivityPub specification. The ForgeFed specification has a relevant detailed section: ## Common properties These would appear in every activity: - `id`: ID URI of the activity - `type`: One of the ActivityPub or ForgeFed activity types - `actor`: ID URI of the actor who published this activity These would appear in some activities: - `capability`: ID URI of a `Grant` activity serving as authorization - `fulfills`: If the activity was published by an automated process rather than human command, these are ID URI(s) of the activities, to which the automated process was reacting ```json { "id": "https://fig.fr33domlover.site/decks/W058b/outbox/nV34D", "type": "Accept", "actor": "https://fig.fr33domlover.site/decks/W058b", "fulfills": [ "https://grape.fr33domlover.site/people/WZpnG/outbox/GQvnR" ], "object": "https://grape.fr33domlover.site/people/WZpnG/outbox/GQvnR" } ``` ## Accept The `actor` has accepted/approved some activity. Always: - `object`: URI of the activity being accepted Sometimes: - `result`: URI of some new entity created as a result of accepting the `object` ## Add The `actor` has requested to add some actor to an authorization-related collection. The cases are everything except adding direct collaborators (which use an `Invite` activity instead): - Add a componet to a project - Add a component/project to a team - Create a parent-child link between a pair of projects/teams Always: - `object`: URI of object being added - `target`: URI of the collection - `instrument`: Maximal role (see `Role` type in ForgeFed spec) of authorizations that will be passed through this newly formed link To determine the case, grab the `target` collection's owning actor, pointed via the collection's `context` field. Now you can example the `target` and `object` actor types, as well as which field of the `target` actor specifies the collection: - `components` of a `Project` - `context` (i.e. projects) of a component - `subteams` of a `Team` - `context` (i.e. parents) of a `Team` - `subprojects` of a `Project` - `context` (i.e. parents) of a `Project` - `teams` of a component/`Project` - `teamResources` of a `Team` The `Add` activity is usually just the first step in a sequence of activities that create the desired authorization link. The activity sequences are described in detail in the specification, e.g. in these sections: - - ## Apply ## Create An actor has published a new object/resource, specified in the `object` field: - A comment on an issue/PR (`Note`) - An issue (`Ticket`) - A component (`TicketTracker`, `PatchTracker`, `Repository`) - `Team` - `Project` ## Follow The `actor` has requested to follow the actor/object specified by the `object` field. ## Grant See . ## Invite Except `target` specifies the *collection*, not the resource itself. For a `Team`, that would be the URI of its `members` collection. For other actor types, it would be the URI of the `collaborators` collection. ## Join Except `object` specifies the *collection*, not the resource itself. For a `Team`, that would be the URI of its `members` collection. For other actor types, it would be the URI of the `collaborators` collection. ## Offer - - ## Push ## Reject ## Remove ## Resolve ## Revoke ## Undo # Publishing and Manipulating Objects All object manipulation in Vervis is done using the ActivityPub C2S API, i.e. by POSTing `Activity` objects to the user's `outbox`. To determine the outbox URI, you can HTTP GET the Person object as mentioned above, and grab the URI specified by its `outbox` field. ## Common properties There are properties you'd often specify in the `Activity` object, that aren't specific to any activity type. - `type`: `Activity` - `actor`: URI of the person actor - `capability`: URI of a `Grant` activity you've received, as authorization for the action you're requesting - `to`, `cc`, `bto`, `bcc`: Audience, i.e. list of URIs of actors and actor collections ## Accept ## Add ## Create ## Follow ## Invite ## Join ## Offer ## Remove ## Resolve ## Undo