Q-Apps API

  • Introduction
  • Resources
  • Routing
  • Integrating a Javascript app
  • qortalRequest

Introduction

Welcome to the Qortal Q-Apps API documentation!

This section provides comprehensive information and resources for developers looking to build applications on top of Qortal's decentralized ecosystem.

Qortal Q-Apps API offers a powerful set of tools and endpoints that enable seamless integration with the Qortal blockchain. With these APIs, developers can harness the full potential of Qortal's decentralized infrastructure to create innovative applications, explore data, interact with the blockchain, and much more!

Whether you are a seasoned blockchain developer or just starting your journey, this documentation will guide you through the various aspects of the Qortal Q-Apps API.

You will find detailed explanations, commented code explaining each part of an API call, API call response examples, and explanations for the different terminology involved in building Q-Apps.

You can also check out our React tutorial guide on Youtube to gain practical knowledge on building a Q-App. The videos take you step-by-step through the process of building a Q-App, from starting up your React application, to deploying your Q-App to the Qortal network.

Join the growing community of Qortal developers and harness the potential of decentralized applications with the Q-Apps API. Together, let's shape the future of blockchain technology! Happy coding and building!

Major thanks to CalDescent for writing the intial version of this API!!

Resources

Each published item on QDN (Qortal Data Network) is referred to as a "resource". A resource could contain anything from a few characters of text, to a multi-layered directory structure containing thousands of files.

Resources are indexed on-chain, however the data payload is generally stored off-chain, and verified using an on-chain SHA-256 hash. Resources smaller than 240 bytes are fully on-chain, and the CHAIN_COMMENT service can be used to ensure this result, allowing faster data retrieval.

To publish a resource, a user must first register a name, and then include that name when publishing the data. Accounts without a registered name are unable to publish to QDN from a Q-App at this time.

Owning the name grants update privileges to the data. If that name is later sold or transferred, the permission to update that resource is moved to the new owner.

Each QDN resource has 3 important fields:

  • name - the registered name of the account that is publishing the data (which will hold update/edit privileges going forwards).
  • service - the type of content (e.g. IMAGE or JSON). Different services have different validation rules. See list of available services.
  • identifier - an optional string to allow more than one resource to exist for a given name/service combination. For example, the name QortalDemo may wish to publish multiple images. This can be achieved by using a different identifier string for each. The identifier is only unique to the name in question, and so it doesn't matter if another name is using the same service and identifier string.

Shared identifiers

Since an identifier can be used by multiple names, this can be used to the advantage of Q-App developers as it allows for data to be stored in a deterministic location.

An example of this is the user's avatar. This will always be published with service THUMBNAIL and identifier qortal_avatar, along with the user's name. So, an app can display the avatar of a user just by specifying their name when requesting the data. The same applies when publishing data.

'Default' resources

A "default" resource refers to one without an identifier. For example, when a website is published via the UI, it will use the user's name and the service WEBSITE. These do not have an identifier, and are therefore the "default" website for this name. When requesting or publishing data without an identifier, apps can either omit the identifier key entirely, or include "identifier": "default" to indicate that the resource(s) being queried or published do not have an identifier.

Service Types

Public services

The services below are intended to be used for publicly accessible data. The maximum file size is shown for each service that does not use the current general limit of 500 MB.

IMAGE (10 MB)
THUMBNAIL (500 KB)
VIDEO
AUDIO
PODCAST
VOICE (10 MB)
ARBITRARY_DATA
JSON (25 KB)
DOCUMENT
LIST
PLAYLIST
METADATA
BLOG
BLOG_POST
BLOG_COMMENT (500 KB)
GIF_REPOSITORY (25 MB)
ATTACHMENT (50 MB)
FILE
FILES
CHAIN_DATA (239 B)
STORE
PRODUCT
OFFER
COUPON
CODE
PLUGIN
EXTENSION
GAME
ITEM
NFT
DATABASE
SNAPSHOT
COMMENT (500 KB)
CHAIN_COMMENT (239 B)
WEBSITE
APP (50 MB)
QCHAT_ATTACHMENT (1 MB)
QCHAT_IMAGE (500 KB)
QCHAT_AUDIO (10 MB)
QCHAT_VOICE (10 MB)

Private services

For the services below, data is encrypted for a single recipient, and can only be decrypted using the private key of the recipient's wallet.

  • QCHAT_ATTACHMENT_PRIVATE (1 MB)
  • ATTACHMENT_PRIVATE (50 MB)
  • FILE_PRIVATE
  • IMAGE_PRIVATE (10 MB)
  • VIDEO_PRIVATE
  • AUDIO_PRIVATE
  • VOICE_PRIVATE (10 MB)
  • DOCUMENT_PRIVATE
  • MAIL_PRIVATE (5 MB)
  • MESSAGE_PRIVATE (1 MB)

Single vs multi-file resources

Some resources, such as those published with the IMAGE or JSON service, consist of a single file or piece of data (the image or the JSON string). This is the most common type of QDN resource, especially in the context of Q-Apps. These can be published by supplying a base64-encoded string containing the data.

Other resources, such as those published with the WEBSITE, APP, or GIF_REPOSITORY service, can contain multiple files and directories. Publishing these kinds of files is not yet available for Q-Apps, however it is possible to retrieve multi-file resources that are already published. When retrieving this data (via FETCH_QDN_RESOURCE), a filepath must be included to indicate the file that you would like to retrieve. There is no need to specify a filepath for single file resources, as these will automatically return the contents of the single file.

App-specific data

Some apps may want to make all QDN data for a particular service available. However, others may prefer to only deal with data that has been published by their app (if a specific format/schema is being used for instance).

Identifiers can be used to allow app developers to locate data that has been published by their app. The recommended approach for this is to use the app name as a prefix on all identifiers when publishing data.

For instance, an app called MyApp could allow users to publish JSON data. The app could choose to prefix all identifiers with the string myapp_, and then use a random string for each published resource (resulting in identifiers such as myapp_qR5ndZ8v). Then, to locate data that has potentially been published by users of MyApp, it can later search the QDN database for items with "service": "JSON" and "identifier": "myapp_". The SEARCH_QDN_RESOURCES action has a prefix option in order to match identifiers beginning with the supplied string.

Note that QDN is a permissionless system, and therefore it's not possible to verify that a resource was actually published by the app. It is recommended that apps validate the contents of the resource to ensure it is formatted correctly, instead of making assumptions.

Updating resources

To update a resource, it can be overwritten by publishing with the same name, service, and identifier combination. Note that the authenticated account must currently own the name in order to publish an update.

Categories

Resources can have an optional category chosen from the following list.

ART (Art and Design)
AUTOMOTIVE (Automotive)
BEAUTY (Beauty)
BOOKS (Books and Reference)
BUSINESS (Business)
COMMUNICATIONS (Communications)
CRYPTOCURRENCY
(Cryptocurrency and Blockchain)
CULTURE (Culture)
DATING (Dating)
DESIGN (Design)
ENTERTAINMENT (Entertainment)
EVENTS (Events)
FAITH (Faith and Religion)
FASHION (Fashion)
FINANCE (Finance)
FOOD (Food and Drink)
GAMING (Gaming)
GEOGRAPHY (Geography)
HEALTH (Health)
HISTORY (History)
HOME (Home)
KNOWLEDGE (Knowledge Share)
LANGUAGE (Language)
LIFESTYLE (Lifestyle)
MANUFACTURING (Manufacturing)
MAPS (Maps and Navigation)
MUSIC (Music)
NEWS (News)
OTHER (Other)
PETS (Pets)
PHILOSOPHY (Philosophy)
PHOTOGRAPHY (Photography)
POLITICS (Politics)
PRODUCE (Products and Services)
PRODUCTIVITY (Productivity)
PSYCHOLOGY (Psychology)
QORTAL (Qortal)
SCIENCE (Science)
SELF_CARE (Self Care)
SELF_SUFFICIENCY
(Self-Sufficiency and Homesteading)
SHOPPING (Shopping)
SOCIAL (Social)
SOFTWARE (Software)
SPIRITUALITY (Spirituality)
SPORTS (Sports)
STORYTELLING (Storytelling)
TECHNOLOGY (Technology)
TOOLS (Tools)
TRAVEL (Travel)
UNCATEGORIZED (Uncategorized)
VIDEO (Video)
WEATHER (Weather)

Routing

If a non-existent filepath is accessed, the default behaviour of QDN is to return a 404: File not found error. This includes anything published using the WEBSITE service.

However, routing is handled differently for anything published using the APP service.

For apps, QDN automatically sends all unhandled requests to the index file (generally index.html). This allows the app to use custom routing, as it is able to listen on any path. If a file exists at a path, the file itself will be served, and so the request won't be sent to the index file.

It's recommended that all apps return a 404 page if a request isn't able to be routed.

Linking to other QDN websites / resources

The qortal:// protocol can be used to access QDN data from within Qortal websites and apps. The basic format is as follows:

1<a href="qortal://{service}/{name}/{identifier}/{path}">link text</a>

However, the system will support the omission of the identifier and/or path components to allow for simpler URL formats.

A simple link to another website can be achieved with this HTML code:

1<a href="qortal://WEBSITE/QortalDemo">link text</a>

To link to a specific page of another website:

1<a href="qortal://WEBSITE/QortalDemo/minting-leveling/index.html">link text</a>

To link to a standalone resource, such as an avatar

1<a href="qortal://THUMBNAIL/QortalDemo/qortal_avatar">avatar</a>

For cases where you would prefer to explicitly include an identifier (to remove ambiguity) you can use the keyword default to access a resource that doesn't have an identifier. For instance:

1<a href="qortal://WEBSITE/QortalDemo/default">link to root of website</a>
1<a href="qortal://WEBSITE/QortalDemo/default/minting-leveling/index.html">link to subpage of website</a>

Linking to other QDN images

The same applies for images, such as displaying an avatar:

1<img src="qortal://THUMBNAIL/QortalDemo/qortal_avatar" />

...or even an image from an entirely different website:

1<img src="qortal://WEBSITE/AlphaX/assets/img/logo.png" />

Integrating a Javascript app

Javascript apps allow for much more complex integrations with Qortal's blockchain data.

Built in variables

Q-Apps can access several variables that give information aboue the state of the app. These include:

  • _qdnTheme
  • _qdnContext

The theme variable will contain the string 'light' or 'dark' depending on the active theme, while the context variable will contain the string 'gateway' if the app is being accessed through a gateway node.

Api Interaction

Direct API calls

The standard Qortal Core API is available to websites and apps, and can be called directly using a standard AJAX request, such as:

1async function getNameInfo(name) {
2 const response = await fetch("/names/" + name);
3 const nameData = await response.json();
4 console.log("nameData: " + JSON.stringify(nameData));
5}
6const name = await getNameInfo("QortalDemo");

RESPONSE

1{
2 "name": "QortalDemo",
3 "reducedName": "q0rta1dem0",
4 "owner": "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
5 "data": "Registered Name on the Qortal Chain",
6 "registered": 1628962032704,
7 "isForSale": false
8}

However, this only works for read-only data, such as looking up transactions, names, balances, etc. Also, since the address of the logged in account can't be retrieved from the core, apps can't show personalized data with this approach.

Via qortalRequest()

1const name = await qortalRequest({
2 action: "GET_NAME_DATA",
3 name: "QortalDemo"
4});

RESPONSE

1{
2 "name": "QortalDemo",
3 "reducedName": "q0rta1dem0",
4 "owner": "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
5 "data": "Registered Name on the Qortal Chain",
6 "registered": 1628962032704,
7 "isForSale": false
8}

qortalRequest

To take things a step further, the qortalRequest() function can be used to interact with the user, in order to:

  • Request address and public key of the logged in account
  • Publish data to QDN
  • Send chat messages
  • Join groups
  • Deploy ATs (smart contracts)
  • Send QORT or any supported foreign coin
  • Add/remove items from lists

In addition to the above, qortalRequest() also supports many read-only functions that are also available via direct core API calls. Using qortalRequest() helps with futureproofing, as the core APIs can be modified without breaking functionality of existing Q-Apps.

Making a request

Qortal core will automatically inject the qortalRequest() javascript function to all websites/apps, which returns a Promise. This can be used to fetch data or publish data to the Qortal blockchain. This functionality supports async/await, as well as try/catch error handling.

1async function myfunction() {
2 try {
3 let res = await qortalRequest({
4 action: "GET_ACCOUNT_DATA",
5 address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
6 });
7 console.log(res); // Log the response to the console
8
9 } catch(e) {
10 console.log("Error: " + e);
11 }
12}
13myfunction();

Timeouts

Request timeouts are handled automatically when using qortalRequest(). The timeout value will differ based on the action being used - see getDefaultTimeout() in q-apps.js for the current values.

If a request times out it will throw an error - The request timed out - which can be handled by the Q-App.

It is also possible to specify a custom timeout using qortalRequestWithTimeout(request, timeout), however this is discouraged. It's more reliable and futureproof to let the core handle the timeout values.

CREATE_POLL

Create a poll inside your Q-App. To get results of this poll, you would subsequently make a fetch call to /polls​/votes​/${pollName} to get the voting results. The poll name must be unique or else it will throw an error.

This action requires user approval

1await qortalRequest({
2 action: "CREATE_POLL",
3 pollName: "A test poll 3",
4 pollDescription: "Test description",
5 pollOptions: ['option1, option2, option3'],
6 pollOwnerAddress: 'QbpZL12Lh7K2y6xPZure4pix5jH6ViVrF2'
7});

RESPONSE

1{
2 "type": "CREATE_POLL",
3 "timestamp": 1697285826221,
4 "reference": "3Svgda6JMSoKW8xQreHRWwXfzWUqCG7NXae5bJDcezbGgK2km8VVbRGZXdEA3Q6LSDvG6hfk1xjXBawpBgxSAa2B",
5 "fee": "0.01000000",
6 "signature": "3jU9WpEPAvu9iL3cMfVd2AUmn9AijJRzkGCxVtXfpuUFZubM8AFDcbk5XA9m5AhPfsbMDFkSDzPJnkjeLA5GA59E",
7 "txGroupId": 0,
8 "approvalStatus": "NOT_REQUIRED",
9 "creatorAddress": "Qhxphh7g5iNtxAyLLpPMZzp4X85yf2tVam",
10 "owner": "QbpZL12Lh7K2y6xPZure4pix5jH6ViVrF2",
11 "pollName": "A test poll 3",
12 "description": "test description",
13 "pollOptions": [
14 {
15 "optionName": "option1"
16 },
17 {
18 "optionName": "option2"
19 },
20 {
21 "optionName": "option3"
22 }
23 ]
24}

DECRYPT_DATA

1const response = await qortalRequest({
2 action: 'DECRYPT_DATA',
3 encryptedData: base64, // has to be in base64 format
4 publicKey: publicKey // requires the public key of the opposite user with whom you've created the encrypted data.
5});

RESPONSE

1AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZ... // Decrypted data is returned in base64 format

DEPLOY_AT

DEPLOY_AT allows a user to create an AT (Automated Transaction) on the Qortal network. This AT can be used to create a crowdfund that pays out after certain funds are accumulated for example.

*Note: This action is for advanced users only. Please check Discord for more information on Deploying ATs. This action requires user approval.

1await qortalRequest({
2 action: "DEPLOY_AT",
3 creationBytes: "19bxFY2S32GVMj2CVdaBs6ekrUQxY2iDVbcTLA6sgHVeBMh8GXZmC9KVPcrid6qhJyPjdfDRh7jz4w2DGhJiHp6EdTJm5MVir9RU2avabWtFXdJbYiLVfr7VQybBtH2JDATESpeL5RAPTzCoesVCoQASVxAYhHL6J8tJoN6ZKugaQpMHccd45bbriMGzYZACu4pQGKfRGpbDXKVUzFjTLZjkbBfv13TjDgTEfUVD2JQ5dCgivb6Padq2oz9KB25kr9kEMXwtQUFLNmwRQHXebHiLFA4GAUDH8qpHTakhZhvBVqBrkyJvRaCpXkE2F6FfJaiYbSVty25QQGqQmrzMTmbYJjSRbrbEnUHmZJNdhgPybmrkN7iKPGj9gy7i5fTgu9t9bbumC3GZK3cNXJfZ885m4cNH4azCRHmQruyDcr5tXp1wnJcwPtkfn87HdgWee2ftLm8hrmrg27eUQgFjNgJr7TEuBmDmPxQCDZkLftnqdymNk6GgNJ5TXgH2tdSmpF5AEh5jRnV63M7tMMo9ezTuEhtachimUrJg5FpF6Q1BJm6Ytd24Hti8G6LCyXMvDvnWxzrgZXRHQVwaeAcpggxPQL4fx4FihwpRkRXmKV1dfjXkpEMB6vJEV9F73ThBwsD61d4N8GAJ7ubNU6BkEzkrqEedU2EDRVccJVaWN64hXmfDN9ygHSpk7TxhsvLrqAhW5H5nZDAcb25yiZowGhWewv8fy1mCtuhWBfzCPiNLSEakudrC2Z53ZZfGcsH4dih2REk6icdmKG5tAXa4tvqxkzdmSxGmqBTpJNqRdCCEYvAUKs6BsXWYSLzEr9wZ7UKSBG61jFm2ZzDYCqb6EZUWDRzL8nWBirGxGAgJ7jvTWUQtc4Q62sa8MEDQAnJLdcCzivwMnZ5udr5BPmRuhBNUbMDNBCC7CwmBpDLzMEWsa4junPfZXr8h9Y5wXyx9wZjwqq3DYNwYV8bgRTnRmxNuuhoWZiL9SxccUwvnTTD9VXPGKdPoWMZ7EWNv3LSrfxRrwoCLRvjtCEonjYN4hosvn61iR2QXdWajg3r5hH79S5M6GohpdKGKvbsttKjewkkdKVok4m8cn7JYx4F4vbS31zVXQ5VQA4m6R54qEUqEgSZM9i6BfckCLPx2ZznXoJ7UcErocUNZFfpfAqECcExRPnu1fqNd9GfGSQGBpvP6VVoowKSc1bVnJxTJfULwNJFa986Skm3B25rEMX2TAVgPFcJMzPLwctzTfMbYhc6nttPWrBZFSrqHcFyUaPPLK4aKkq6oPa1PA4Cgb4L9sQjksXB3tqnTFGa9ZsQE79QQHQrDVurRFh3fyMfvkAXyVycW1W4QBE6R4h7PvaNbDRXUQtQ8pcHRdY1BSUAXQp1u3XkNES7BmqjNxgDTcBMfUv3",
4 name: "Test AT",
5 description: "QORT/LTC cross-chain trade",
6 type: "ACCT",
7 tags: "Test ACCT QORT LTC",
8 amount: 100000000, // 1 QORT
9 assetId: 0,
10 fee: 20000 // 0.0002 QORT
11});

RESPONSE

1{
2 "type": "DEPLOY_AT",
3 "timestamp": 1696526612286,
4 "reference": "61w5axRcoyUX9dEjQVotiNkrxuLSotHV8iwrfsF5BimLB9vBHuyZMoC59WY2nbcjUPHVw4QFcpEcQcSwxr5tXVXs",
5 "fee": "0.01000000",
6 "signature": "4R8MQAA1cqwYfDRC4KUV7nQYpooFHtM71XLFUxC9uX6Ni59CnswivvsqYVXJkzs3qY9QhHS5F4nEbX3tbXmgUjGE",
7 "txGroupId": 0,
8 "approvalStatus": "NOT_REQUIRED",
9 "creatorAddress": "QWfYVQfuz2rVskYkpkVvLxL3kUPmBgKhHV",
10 "name": "q-fund crowdfund",
11 "description": "qortal-devfund-q-fund-campaign",
12 "aTType": "crowdfund",
13 "tags": "q-fund",
14 "creationBytes": "1Pub6o13xyqfCZj8BMzmXsREVJR6h4xxpS2VPV1R2QwjP78r2ozxsNuvb28GWrT8FoTTQMGnVP7pNii6auUqYr2uunWfcxwhERbDgFdsJqtrJMpQNGB9GerAXYyiFiij35cP6eHw7BmALb3viT6VzqaXX9YB25iztekV5cTreJg7o2hRpFc9Rv8Z9dFXcD1Mm4WCaMaknUgchDi7qDnHA7JX8bn9EFD4WMG5nZHMsrmeqBHirURXr2dMxFprTBo187zztn9bFAmGdDBxgKg2qkPo7ZX3QFNEsUXeynXM7SJzHzdKaECxbefkPfA1nGENnM1Mto6rjWCX4KZk6QtrUtYBLGHo5XWkXGm9VJmywUbRdJviNbAN23VLBq2Q7AFAeGCkFSTYvomqjNUDUDNRjDH",
15 "amount": "0.20000000",
16 "assetId": 0,
17 "aTAddress": "AJQRHEdunFaZpufYfCmqjKBoWYnU6j6jfa",
18 "creatorPublicKey": "8i9bPX8Mf8wJRKf4GiSAqWzCBsCNkCtWPJ9hxuHMgGRo",
19 "blocksToGoal": 37435,
20 "goalValue": 25000,
21 "userAddress": "QWfYVQfuz2rVskYkpkVvLxL3kUPmBgKhHV"
22}

ENCRYPT_DATA

Encrypts data provided by the user. This handler takes in data, optionally in the form of a file, or a base64 string, and an array of public keys to perform group encryption. Returns the encrypted data in a JSON string.

1const encryptedData = await qortalRequest({
2 action: 'ENCRYPT_DATA',
3 file: file, // requires a file object
4 base64: base64 // requires a base64 string if file object is not provided
5 publicKeys: [] // Array of public keys of the users with whom you want to encrypt the data (optional, used for group encryption)
6});

RESPONSE

1"cW9ydGFsR3JvdXBFbmNyeXB0ZWREYXRhmixnlZeFIMyu8mrCtewxFyGdWu9HBqfyvSLOlY7tl2e0tUbILFNblFyN5dzJYfJ1pLBZNUBmBc21mZ9CP0vWLpoUW65823McoD4oQ1HmaVhwZJRicAtelcimB3bMF6TlvthWCicvcoO6S76XI2vVI1WLEdUEFgbvt8cVfvDboz+GWbFRvNmADmKaXDeM8yAJVsRo19Ot6AEAAAA="

FETCH_BLOCK

Fetch block by signature

1const response = await qortalRequest({
2 action: "FETCH_BLOCK",
3 signature: "875yGFUy1zHV2hmxNWzrhtn9S1zkeD7SQppwdXFysvTXrankCHCz4iyAUgCBM3GjvibbnyRQpriuy1cyu953U1u5uQdzuH3QjQivi9UVwz86z1Akn17MGd5Z5STjpDT7248K6vzMamuqDei57Znonr8GGgn8yyyABn35CbZUCeAuXju"
4});

RESPONSE

1{
2 "signature": "MPBrvZ8xt28tz2L6jdLnudJrudGoy22BBmkmfu5r7wU4QH3hbVBnxL7fzavR8YHx4SD8bdzgJJaHHeHgDL4D5UwTdhobAoxbFmyHLirnG1G6hgDmZDhKzmyEWCdoNGyn4bsLyKZJ4axo3yRHt7ZTUNLqq7hMc5rwTqYJX3FgyXncN3P",
3 "version": 4,
4 "reference": "875yGFUy1zHV2hmxNWzrhtn9S1zkeD7SQppwdXFysvTXrankCHCz4iyAUgCBM3GjvibbnyRQpriuy1cyu953U1u5uQdzuH3QjQivi9UVwz86z1Akn17MGd5Z5STjpDT7248K6vzMamuqDei57Znonr8GGgn8yyyABn35CbZUCeAuXju",
5 "transactionCount": 0,
6 "totalFees": "0.00003520",
7 "transactionsSignature": "5yCkerB1Ed57vBPyWWH8ZR6Hv5LGC8zqBLPiLHKeQE1gNfc4yByQvSAHQKJZiqShWfpFEf9VmvmAwVsaFZrCqUe9",
8 "height": 1139852,
9 "timestamp": 1674130634871,
10 "minterPublicKey": "6g5K1X4bZFWn1S3buye6qFMGTyZug4G96Gif4DZ2E7jV",
11 "minterSignature": "4yyX2xrSQdzvZAFbvK8okwT2DDJMiTSin3wGio3858v1FXhXDT9qKC1RQfCrDNzgWA9L1gSQ8ypMZJ5y4MMMxe14",
12 "atCount": 180,
13 "atFees": "0.00003520",
14 "encodedOnlineAccounts": "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",
15 "onlineAccountsCount": 4707,
16 "minterAddress": "QVLSru2TzBrgAmqJMzTwsBxwm98ABfGsdj"
17}

Fetch block by height

1const response = await qortalRequest({
2 action: "FETCH_BLOCK",
3 height: "1139850"
4});

RESPONSE

1// same response as 'Fetch block by signature'

Fetch a range of blocks

Equivalent to: /blocks/range/{height}

1const response = await qortalRequest({
2 action: "FETCH_BLOCK_RANGE",
3 height: "1139800",
4 count: 20,
5 reverse: false
6});

RESPONSE

1[
2 // an array of objects such as the response of 'Fetch block by signature'
3]

FETCH_QDN_RESOURCE

1const response = await qortalRequest({
2 action: "FETCH_QDN_RESOURCE",
3 name: "QortalDemo",
4 service: "THUMBNAIL",
5 identifier: "qortal_avatar", // Optional. If omitted, the default resource is returned, or you can alternatively use the keyword "default"
6 encoding: "base64", // Optional. If omitted, data is returned in raw form
7 rebuild: false
8});

RESPONSE

1// base64 encoding returns a base64
2AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAA1HbWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpd...
1// In this example I'm requesting a json that was saved to QDN (this is just an example- this exact resource doesn't exist)
2const response = await qortalRequest({
3 action: 'FETCH_QDN_RESOURCE',
4 name: 'Q-Blog',
5 service: 'BLOG_POST',
6 identifier: 'q-blog-tester-2-blog-post-test-HDUgPk'
7})

RESPONSE

1// If the encoding param is omitted the raw data is return. In this case a json.
2{
3 "title": "test zip",
4 "createdAt": 1683806014297,
5 "postContent": [
6 {
7 "type": "file",
8 "version": 1,
9 "content": {
10 "name": "Q-Blog",
11 "identifier": "qfile_qblog_X5uIea",
12 "service": "FILE",
13 "title": "tester2's file zip",
14 "mimeType": "application/zip"
15 },
16 "id": "SkMHKY"
17 }
18 ],
19 "layouts": {
20 "rows": [
21 {
22 "ids": [
23 "SkMHKY"
24 ],
25 "id": "UHZmoI",
26 "type": "file"
27 }
28 ]
29 },
30 "layoutGeneralSettings": {
31 "padding": 5,
32 "blogPostType": "minimal"
33 }
34}

Fetch file from multi file QDN resource

Data is returned in the base64 format

1const response = await qortalRequest({
2 action: "FETCH_QDN_RESOURCE",
3 name: "QortalDemo",
4 service: "WEBSITE",
5 identifier: "default", // Optional. If omitted, the default resource is returned, or you can alternatively request that using the keyword "default", as shown here
6 filepath: "index.html", // Required only for resources containing more than one file
7 rebuild: false
8});

GET_ACCOUNT_DATA

1const response = await qortalRequest({
2 action: "GET_ACCOUNT_DATA",
3 address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
4});

RESPONSE

1{
2 "address": "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
3 "reference": "4uEPpCYsk2rCUsxksP72PeRtHaR5vQg9kWy6mLU377a7H4RJtCRFVCT6anPdfdXRs3p7f8KYo8LtZLmntQ7eNoks",
4 "publicKey": "APLQ85zRbgRdrLTU7GgeTt35kvVhxmSjoCB4wX99HjYd",
5 "defaultGroupId": 205,
6 "flags": 0,
7 "level": 0,
8 "blocksMinted": 0,
9 "blocksMintedAdjustment": 0,
10 "blocksMintedPenalty": 0
11}

GET_ACCOUNT_NAMES

1const response = await qortalRequest({
2 action: "GET_ACCOUNT_NAMES",
3 address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
4 limit: 20,
5 offset: 0,
6 reverse: true
7});

RESPONSE

1[
2 {
3 "name": "QortalDemo",
4 "owner": "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
5 }
6]

GET_BALANCE

1const response = await qortalRequest({
2 action: "GET_BALANCE",
3 address: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2"
4});

RESPONSE

17.11

GET_DAY_SUMMARY

1const summary = await qortalRequest({
2 action: "GET_DAY_SUMMARY"
3});

RESPONSE

1{
2 "blockCount": 1195,
3 "assetsIssued": 0,
4 "namesRegistered": 7,
5 "transactionCountByType": {
6 "PAYMENT": 31,
7 "REGISTER_NAME": 7,
8 "SELL_NAME": 7,
9 "CANCEL_SELL_NAME": 1,
10 "ARBITRARY": 94,
11 "TRANSFER_ASSET": 1677,
12 "DEPLOY_AT": 43,
13 "MESSAGE": 61,
14 "AT": 54,
15 "GROUP_INVITE": 2,
16 "JOIN_GROUP": 3,
17 "REWARD_SHARE": 60
18 },
19 "totalTransactionCount": 2040
20}

GET_FRIENDS_LIST

This action requires user approval

Gets a user's friends list in an array of strings. Returns an error "Error in retrieving friends list" if it is not successful.

1const friendsList = await qortalRequest({
2 action: "GET_FRIENDS_LIST"
3});

RESPONSE

1[
2 "Phil",
3 "Qortal Seth",
4 "Justin"
5]

GET_NAME_DATA

Equivalent to: /names/QortalDemo

1const response = await qortalRequest({
2 action: "GET_NAME_DATA",
3 name: "QortalDemo"
4});

RESPONSE

1{
2 "name": "QortalDemo",
3 "reducedName": "q0rta1dem0",
4 "owner": "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
5 "data": "Registered Name on the Qortal Chain",
6 "registered": 1628962032704,
7 "isForSale": false
8}

GET_PRICE

Equivalent to: /crosschain/price/{blockchain}

The value of each Qortal unit is expressed in terms of 1 Qort multiplied by 1e8. Hence, if you receive a response value of 303366, you simply divide it by 1e8 to find its equivalent in Qort. This conversion will yield a result of 0.0030366 Qort.

1const response = await qortalRequest({
2 action: "GET_PRICE",
3 blockchain: "LITECOIN",
4 // maxtrades: 10,
5 inverse: true //Display price in terms of foreign currency per unit QORT
6});

RESPONSE

1303366

GET_QDN_RESOURCE_METADATA

Equivalent to: /arbitrary/metadata/{service}/{name}/{identifier}

If the resource doesn't have metedata, a 404 will be returned

1const response = await qortalRequest({
2 action: "GET_QDN_RESOURCE_METADATA",
3 name: "Q-Blog",
4 service: "BLOG_POST",
5 identifier: "q-blog-qblog-post-Publishing-Blog-s-SncAO3" // Optional
6});

RESPONSE

1{
2 "title": "Publishing Blog Posts",
3 "description": "Learn how to publish blog posts. There are two types of editors. Find out which one is for you!",
4 "tags": [
5 "v1.0x0",
6 "t, v"
7 ],
8 "files": [
9 "qortal-1682200171884"
10 ],
11 "mimeType": "text/vnd.graphviz"
12}

GET_QDN_RESOURCE_PROPERTIES

1const response = await qortalRequest({
2 action: "GET_QDN_RESOURCE_PROPERTIES",
3 name: "QortalDemo",
4 service: "THUMBNAIL",
5 identifier: "qortal_avatar" // Optional
6});

RESPONSE

1{
2 "filename": "qortal-1684173273893",
3 "mimeType": "video/mp4",
4 "size": 23766987
5}

GET_QDN_RESOURCE_STATUS

1const response = await qortalRequest({
2 action: "GET_QDN_RESOURCE_STATUS",
3 name: "QortalDemo",
4 service: "THUMBNAIL",
5 identifier: "qortal_avatar", // Optional
6 build: true // Optional - request that the resource is fetched & built in the background
7});

RESPONSE

1{
2 "status": "READY",
3 "id": "READY",
4 "title": "Ready",
5 "description": "Ready",
6 "localChunkCount": 45,
7 "totalChunkCount": 45,
8 "percentLoaded": 100.0
9}

GET_QDN_RESOURCE_URL

Get URL to load a QDN resource. Note: this returns a "Resource does not exist" error if a non-existent resource is requested.

1let url = await qortalRequest({
2 action: "GET_QDN_RESOURCE_URL",
3 service: "THUMBNAIL",
4 name: "QortalDemo",
5 identifier: "qortal_avatar"
6 // path: "filename.jpg" // optional - not needed if resource contains only one file
7});
8Get URL to load a QDN website
9Note: this returns a "Resource does not exist" error if a non-existent resource is requested.
10
11let url = await qortalRequest({
12 action: "GET_QDN_RESOURCE_URL",
13 service: "WEBSITE",
14 name: "QortalDemo",
15});
16Get URL to load a specific file from a QDN website
17Note: this returns a "Resource does not exist" error if a non-existent resource is requested.
18
19let url = await qortalRequest({
20 action: "GET_QDN_RESOURCE_URL",
21 service: "WEBSITE",
22 name: "AlphaX",
23 path: "/assets/img/logo.png"
24});

RESPONSE

1'/arbitrary/BLOG_POST/Q-Blog/q-blog-qblog-post-Publishing-Blog-s-SncAO3'

GET_PROFILE_DATA

Get a profile property. Returns a user's profile property data based on inputted key. This will return a string value if the property is any of the following: "tagline" or "bio". Both "wallets" and custom properties will return an object.

Custom properties are created by the user and can be any key-value pair. They are created by using the "SET_PROFILE_DATA" action. Trying to get a custom property that does not exist will return an error object {error: "Cannot find requested data"}.

1const profileData = await qortalRequest({
2 action: "GET_PROFILE_DATA",
3 property: "wallets"
4});

RESPONSE

1{
2 "btc": "12zcTs6EkpVcJCTPWTGYkqukW2W8iv81KA",
3 "ltc": "LhqJGB78YajuuiysXZBhiF1YcttRNwzBgD",
4 "doge": "D77Z9AZXhmbuKqRvTqoF7p4bmbGGdAaKPZ",
5 "dgb": "DURXrj6V6yHbEKm529sW7yBKG3Gm1qWbqb",
6 "rvn": "RCJJq72k5pzfczp51BZfi3CHLuq422JBF1",
7 "arrr": "zs1shd0rle4yvnafwzl9nwqzvwxdx3ew803hjw0qc2zhvrx5vfxdtvmh8wgwjlpe5elrhszx4yyzme"
8}

Get a profile custom property

1const profileData = await qortalRequest({
2 action: "GET_PROFILE_DATA",
3 property: "foo"
4});

RESPONSE

1{
2 newKey: "bar"
3}

GET_USER_ACCOUNT

This action requires user approval

1const account = await qortalRequest({
2 action: "GET_USER_ACCOUNT"
3});

RESPONSE

1{
2 "address": "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
3 "publicKey": "APLQ85zRbgRdrLTU7GgeTt35kvVhxmSjoCB4wX99HjYd",
4}

GET_USER_WALLET

Choose which coin you want to get the wallet for

This action requires user approval

1const response = await qortalRequest({
2 action: "GET_USER_WALLET",
3 coin: "QORT"
4});

RESPONSE

17.11

GET_USER_WALLET_INFO

The GET_USER_WALLET_INFO endpoint retrieves detailed information for each address associated with a user's Hierarchical Deterministic (HD) BIP32 wallet. It provides a structured overview of wallet addresses, allowing users to manage and organize their cryptocurrency transactions efficiently.

This action requires user approval

1const response = await qortalRequestWithTimeout({
2 action: "GET_USER_WALLET_INFO",
3 coin: "BTC"
4}, 15000);

RESPONSE

1[
2 {
3 "address": "1KyDZurCr2S15N2j1s4pUeH8fAQVT7VfdB",
4 "path": [
5 0,
6 0
7 ],
8 "value": 0,
9 "pathAsString": "M/0/0",
10 "transactionCount": 0
11 },
12 {
13 "address": "1DYn4oyypvyFCFL7Y5oDoAfmem88SucxEJ",
14 "path": [
15 0,
16 1
17 ],
18 "value": 0,
19 "pathAsString": "M/0/1",
20 "transactionCount": 0
21 },
22 // Additional addresses omitted for brevity
23]

GET_WALLET_BALANCE

This action requires user approval

1const response = await qortalRequest({
2 action: "GET_WALLET_BALANCE",
3 coin: "QORT"
4});

RESPONSE

17.11

JOIN_GROUP

This action requires user approval

1const response = await qortalRequest({
2 action: "JOIN_GROUP",
3 groupId: 100
4});

RESPONSE

1true

LIST_QDN_RESOURCES

Equivalent to: /arbitrary/resources?

1const response = await qortalRequest({
2 action: "LIST_QDN_RESOURCES",
3 service: "THUMBNAIL",
4 name: "QortalDemo", // Optional (exact match)
5 identifier: "qortal_avatar", // Optional (exact match)
6 default: true, // Optional
7 includeStatus: false, // Optional - will take time to respond, so only request if necessary
8 includeMetadata: false, // Optional - will take time to respond, so only request if necessary
9 followedOnly: false, // Optional - include followed names only
10 excludeBlocked: false, // Optional - exclude blocked content
11 limit: 100,
12 offset: 0,
13 reverse: true
14});

RESPONSE

1[
2 // without metadata
3 {
4 "name": "crowetic",
5 "service": "FILE",
6 "identifier": "qfile_qblog_E1V14x",
7 "size": 2949936
8 },
9 // with metadata (includeMetadata: true)
10 {
11 "name": "crowetic",
12 "service": "FILE",
13 "identifier": "qfile_qblog_uPsr9L",
14 "metadata": {
15 "title": "Qortal-The-Future-Wallpaper",
16 "description": "This is mostly a test of Q-Blog published image in file format on website links",
17 "tags": [
18 "Image testing"
19 ],
20 "category": "QORTAL",
21 "categoryName": "Qortal",
22 "mimeType": "image/png"
23 },
24 "size": 2108432
25 },
26]

LIST_GROUPS

Equivalent to: /groups?

1const response = await qortalRequest({
2 action: "LIST_GROUPS",
3 limit: 100,
4 offset: 0,
5 reverse: true
6});

RESPONSE

1[
2 {
3 "groupId": 150,
4 "owner": "QRt11DVBnLaSDxr2KHvx92LdPrjhbhJtkj",
5 "groupName": "animation station",
6 "description": "freedom cartoons... made here",
7 "created": 1638219000069,
8 "isOpen": false,
9 "approvalThreshold": "ONE",
10 "minimumBlockDelay": 5,
11 "maximumBlockDelay": 60,
12 "memberCount": 1
13 },
14 {
15 "groupId": 121,
16 "owner": "QLkRktaMTcYMKNMTxvLdh9rgsWbhVth2A1",
17 "groupName": "anime",
18 "description": "talk about it, watch it, whatever you like, this group is about anime",
19 "created": 1624914222641,
20 "isOpen": true,
21 "approvalThreshold": "ONE",
22 "minimumBlockDelay": 5,
23 "maximumBlockDelay": 1440,
24 "memberCount": 7
25 },
26 ...
27]

LISTS

GET_LIST_ITEMS

This action requires user approval

1const response = await qortalRequest({
2 action: "GET_LIST_ITEMS",
3 list_name: "blockedNames"
4});

RESPONSE

1[ 'Q-Blog', 'Q-Mail']

ADD_LIST_ITEMS

This action requires user approval

1const response = await qortalRequest({
2 action: "ADD_LIST_ITEMS",
3 list_name: "blockedNames",
4 items: ["QortalDemo"]
5});
6
7// the list will become [ 'Q-Blog', 'Q-Mail', 'QortalDemo]

RESPONSE

1true

DELETE_LIST_ITEM

This action requires user approval

1const response = await await qortalRequest({
2 action: "DELETE_LIST_ITEM",
3 list_name: "blockedNames",
4 item: "Q-MAIL"
5});
6// the list will become [ 'Q-Blog', 'QortalDemo]

RESPONSE

1true

NOTIFICATIONS_PERMISSION

Request permission from the user to receive notifications from your Q-App

1const response = await qortalRequest({
2 action: 'NOTIFICATIONS_PERMISSION'
3})

Response if the user accepts the modal.

RESPONSE

1true

Response if the user declines the modal.

RESPONSE

1false

OPEN_NEW_TAB

Opens a new tab with the specified URL inside the Qortal UI

1const response = await qortalRequest({
2 action: 'OPEN_NEW_TAB',
3 qortalLink: 'qortal://APP/Ear-Bump/liked'
4})

RESPONSE

1true

OPEN_PROFILE

Opens a user's profile if they have one. Note: this will open a modal with "This name has no profile" if that is the case.

1const profile = await qortalRequest({
2 action: "OPEN_PROFILE",
3 name: "Bester"
4});

RESPONSE

1true

PUBLISH_QDN_RESOURCE

This action requires user approval

Note: this publishes a single file object or base64-encoded file. Compressed archive (zip) and multi-file resource publishing (such as a WEBSITE or GIF_REPOSITORY) is not yet supported via a Q-App. It may be added in a future update.

1const response = await qortalRequest({
2 action: "PUBLISH_QDN_RESOURCE",
3 name: "Demo", // Publisher must own the registered name - use GET_ACCOUNT_NAMES for a list
4 service: "IMAGE",
5 identifier: "myapp-image1234" // Optional
6 data64: "base64_encoded_data", // base64 string. Remove this param if you are putting in a file. see next param.
7 file: "file_object", // File Object. Remove this param if you are putting in a base64 string.
8 // filename: "image.jpg", // Optional - to help apps determine the file's type
9 // title: "Title", // Optional
10 // description: "Description", // Optional
11 // category: "TECHNOLOGY", // Optional
12 // tag1: "any", // Optional
13 // tag2: "strings", // Optional
14 // tag3: "can", // Optional
15 // tag4: "go", // Optional
16 // tag5: "here", // Optional
17 // encrypt: true, // Optional - to be used with a private service
18 // recipientPublicKey: "publickeygoeshere" // Only required if encrypt is set to true
19});

RESPONSE

1{
2 "type": "ARBITRARY",
3 "timestamp": 1684337616751,
4 "reference": "4j2iPN5Xwgocs8Z32JB4UB63G87qS43kPyEwFmQMLvWBXtrSQwAfyx8S9CqQvbregnstXFKqXpkPT2dNdAscriT4",
5 "fee": "0.00100000",
6 "signature": "5Y6Jdyvo8n6JB44qC8i57x2gA3VXd7aah42GvnzkXL459vCWrRE5gae5TWbGqJxmKnk4p5ESHESv2FJfyB8bdzs9",
7 "txGroupId": 0,
8 "approvalStatus": "NOT_REQUIRED",
9 "creatorAddress": "QMjCNsctvWLoDdPSRpHn6TF2j96iDr9YWm",
10 "version": 5,
11 "senderPublicKey": "Bjo1iUHJXbCb4LKabmE6KWNL5jSgCK36ypasoDgJG53U",
12 "service": 777,
13 "nonce": 0,
14 "size": 496,
15 "name": "Demo",
16 "identifier": "myapp-image1234",
17 "method": "PUT",
18 "secret": "GELP1wMCyMAcwLWTvsAjsFoiR63BcAF7wmrhd1NVhjL",
19 "compression": "ZIP",
20 "data": "ArzaQASrXM74SDhWp6JhhmUBsocWRoVajZFtYECnAEEy",
21 "dataType": "DATA_HASH",
22 "metadataHash": "DU1sYzPcRnvAUL6VzerhAmDQUyrn4UceAvV4K6WtZxBi",
23 "payments": []
24}

PUBLISH_MULTIPLE_QDN_RESOURCES

1const resourceArray = [
2 {
3 name: "Demo",
4 service: "IMAGE",
5 identifier: "myapp-image1"
6 file: "file_object",
7 filename: "image1.png"
8 },
9 {
10 name: "Demo",
11 service: "IMAGE",
12 identifier: "myapp-image2"
13 file: "file_object",
14 filename: "image2.png"
15 }
16]
17const response = await qortalRequest({
18 action: 'PUBLISH_MULTIPLE_QDN_RESOURCES',
19 resources: resourceArray
20})

RESPONSE

1[
2 {
3 "type": "ARBITRARY",
4 "timestamp": 1684337616751,
5 "reference": "4j2iPN5Xwgocs8Z32JB4UB63G87qS43kPyEwFmQMLvWBXtrSQwAfyx8S9CqQvbregnstXFKqXpkPT2dNdAscriT4",
6 "fee": "0.00100000",
7 "signature": "5Y6Jdyvo8n6JB44qC8i57x2gA3VXd7aah42GvnzkXL459vCWrRE5gae5TWbGqJxmKnk4p5ESHESv2FJfyB8bdzs9",
8 "txGroupId": 0,
9 "approvalStatus": "NOT_REQUIRED",
10 "creatorAddress": "QMjCNsctvWLoDdPSRpHn6TF2j96iDr9YWm",
11 "version": 5,
12 "senderPublicKey": "Bjo1iUHJXbCb4LKabmE6KWNL5jSgCK36ypasoDgJG53U",
13 "service": 777,
14 "nonce": 0,
15 "size": 496,
16 "name": "Demo",
17 "identifier": "myapp-image1",
18 "method": "PUT",
19 "secret": "GELP1wMCyMAcwLWTvsAjsFoiR63BcAF7wmrhd1NVhjL",
20 "compression": "ZIP",
21 "data": "ArzaQASrXM74SDhWp6JhhmUBsocWRoVajZFtYECnAEEy",
22 "dataType": "DATA_HASH",
23 "metadataHash": "DU1sYzPcRnvAUL6VzerhAmDQUyrn4UceAvV4K6WtZxBi",
24 "payments": []
25 },
26 {
27 "type": "ARBITRARY",
28 "timestamp": 1684337616751,
29 "reference": "4j2iPN5Xwgocs8Z32JB4UB63G87qS43kPyEwFmQMLvWBXtrSQwAfyx8S9CqQvbregnstXFKqXpkPT2dNdAscriT4",
30 "fee": "0.00100000",
31 "signature": "5Y6Jdyvo8n6JB44qC8i57x2gA3VXd7aah42GvnzkXL459vCWrRE5gae5TWbGqJxmKnk4p5ESHESv2FJfyB8bdzs9",
32 "txGroupId": 0,
33 "approvalStatus": "NOT_REQUIRED",
34 "creatorAddress": "QMjCNsctvWLoDdPSRpHn6TF2j96iDr9YWm",
35 "version": 5,
36 "senderPublicKey": "Bjo1iUHJXbCb4LKabmE6KWNL5jSgCK36ypasoDgJG53U",
37 "service": 777,
38 "nonce": 0,
39 "size": 496,
40 "name": "Demo",
41 "identifier": "myapp-image2",
42 "method": "PUT",
43 "secret": "GELP1wMCyMAcwLWTvsAjsFoiR63BcAF7wmrhd1NVhjL",
44 "compression": "ZIP",
45 "data": "ArzaQASrXM74SDhWp6JhhmUBsocWRoVajZFtYECnAEEy",
46 "dataType": "DATA_HASH",
47 "metadataHash": "DU1sYzPcRnvAUL6VzerhAmDQUyrn4UceAvV4K6WtZxBi",
48 "payments": []
49 }
50]

SAVE_FILE

This action requires user approval

1await qortalRequest({
2 action: "SAVE_FILE",
3 blob: dataBlob,
4 filename: "myfile.pdf",
5 mimeType: "application/pdf" // Optional but recommended
6});

SEARCH_NAMES

1const response = await qortalRequest({
2 action: "SEARCH_NAMES",
3 query: "search query goes here",
4 prefix: false, // Optional - if true, only the beginning of the name is matched
5 limit: 100,
6 offset: 0,
7 reverse: false
8})

RESPONSE

1[
2 {
3 "name": "... Playing Qortal ...",
4 "reducedName": "... p1aying q0rta1 ...",
5 "owner": "QNwKEgYaJzF7MTZzFMJUnQgkemiac18bf6",
6 "data": "The most interactive game in the World!",
7 "registered": 1644330871648,
8 "isForSale": false
9 },
10 {
11 "name": "10Qortal",
12 "reducedName": "10q0rta1",
13 "owner": "QhLt2v3TDJrBPCa64ogUna785T6sRm5dxJ",
14 "data": "Registered Name on the Qortal Chain",
15 "registered": 1644995489154,
16 "isForSale": false
17 },
18]

SEARCH_QDN_RESOURCES

1const response = await qortalRequest({
2 action: "SEARCH_QDN_RESOURCES",
3 service: "THUMBNAIL",
4 query: "search query goes here", // Optional - searches both "identifier" and "name" fields
5 identifier: "search query goes here", // Optional - searches only the "identifier" field
6 name: "search query goes here", // Optional - searches only the "name" field
7 prefix: false, // Optional - if true, only the beginning of fields are matched in all of the above filters
8 exactMatchNames: true, // Optional - if true, partial name matches are excluded
9 default: false, // Optional - if true, only resources without identifiers are returned
10 includeStatus: false, // Optional - will take time to respond, so only request if necessary
11 includeMetadata: false, // Optional - will take time to respond, so only request if necessary
12 nameListFilter: "QApp1234Subscriptions", // Optional - will only return results if they are from a name included in supplied list
13 followedOnly: false, // Optional - include followed names only
14 excludeBlocked: false, // Optional - exclude blocked content
15 limit: 100,
16 offset: 0,
17 reverse: true
18});

RESPONSE

1[
2 // with includemetadata:true
3 {
4 "name": "Q-Blog",
5 "service": "BLOG_POST",
6 "identifier": "q-blog-qblog-post-Publishing-Blog-s-SncAO3",
7 "metadata": {
8 "title": "Publishing Blog Posts",
9 "description": "Learn how to publish blog posts. There are two types of editors. Find out which one is for you!",
10 "tags": [
11 "v1.0x0",
12 "t, v"
13 ],
14 "mimeType": "text/vnd.graphviz"
15 },
16 "size": 928,
17 "created": 1682190828187,
18 "updated": 1682200172086
19 },
20 // without includemetadata
21 {
22 "name": "Q-Blog",
23 "service": "BLOG_POST",
24 "identifier": "q-blog-qblog-post-Publishing-Blog-s-SncAO3",
25 "size": 928,
26 "created": 1682190828187,
27 "updated": 1682200172086
28 },
29]

SEARCH_CHAT_MESSAGES

Equivalent to: /chat/messages?

This action requires user approval

1const response = await qortalRequest({
2 action: "SEARCH_CHAT_MESSAGES",
3 before: 999999999999999,
4 after: 0,
5 txGroupId: 0, // Optional (must specify either txGroupId or two involving addresses)
6 // involving: ["QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2", "QSefrppsDCsZebcwrqiM1gNbWq7YMDXtG2"], // Optional (must specify either txGroupId or two involving addresses)
7 // reference: "reference", // Optional
8 // chatReference: "chatreference", // Optional
9 // hasChatReference: true, // Optional
10 encoding: "BASE64", // Optional (defaults to BASE58 if omitted)
11 limit: 20,
12 offset: 0,
13 reverse: true
14});

RESPONSE

1[
2 {
3 "timestamp": 1684326780846,
4 "txGroupId": 0,
5 "reference": "BRr2qZEts2QjoGh9fWGNSnKJiCr92vXDBw9aPYYKNDFq4iSxtFpBnh7giJK5jiP5YGNQTbySu3kSnHGZbHf6HL9",
6 "senderPublicKey": "HpFimVDQeSb3yCaibrpLLfm8k5j3RsQ9K8VqT2YUiUAb",
7 "sender": "QfMPvGEBJMhhNSfhNqRy6qRYvybdnQAiuJ",
8 "senderName": "Stonecrow",
9 "encoding": "BASE64",
10 "data": "eyJtZXNzYWdlVGV4dCI6eyJ0eXBlIjoiZG9jIiwiY29udGVudCI6W3sidHlwZSI6InBhcmFncmFwaCIsImNvbnRlbnQiOlt7InR5cGUiOiJ0ZXh0IiwidGV4dCI6Imdvb2RuaWdodCBDYXQuwqAgQmFjayBpbiB5dXIgYm94IG5vdy4gVGhhdCBuYXN0eSB0cmVlIHdvbnQgYm90aGVyIHlvdSBubyBtb3JlLiJ9XX1dfSwiaW1hZ2VzIjpbIiJdLCJyZXBsaWVkVG8iOiIiLCJ2ZXJzaW9uIjozfQ==",
11 "isText": true,
12 "isEncrypted": false,
13 "signature": "4ja9XuNReuhn1Gh7uPvZXxZZnFVxXejvRFphQUZNC2FRqWn3Jv9T8oXnU6dxoGTSeYhiW8GUzkqmHZFaFLFibFWd"
14 },
15 {
16 "timestamp": 1684326755079,
17 "txGroupId": 0,
18 "reference": "5o8YcSvPEY2yV6o8DxQcQgK9tAdHjncT4K6n1oXeJo51yUEXxQ9doMrFRnaVEb3QHZmAtvcWDkd1fojqcuNWbDcP",
19 "senderPublicKey": "AKLvYPJLqJTVovRThNu33vfhWTPFPZH5DpjguiNAacvG",
20 "sender": "QfKKzGktu6FzNGWrzxcmVJZ3SJV53PMtTw",
21 "senderName": "Dreaming_Loudly",
22 "encoding": "BASE64",
23 "data": "eyJtZXNzYWdlVGV4dCI6eyJ0eXBlIjoiZG9jIiwiY29udGVudCI6W3sidHlwZSI6InBhcmFncmFwaCIsImNvbnRlbnQiOlt7InR5cGUiOiJ0ZXh0IiwidGV4dCI6Ikh1cnJ5IHVwIC0gSSdtIGdldHRpbmcgY29sZCBseWluZyBoZXJlIC3CoPCfmJsifV19XX0sImltYWdlcyI6WyIiXSwicmVwbGllZFRvIjoiNDRTODhIZ0hqcUJ1RnF5WkNyaDMxUGdkWUZhWEpaVzFTWlZlempEVVprdHg3WWZ1Q0RvWXZOOWRzWVNvUVM4cTlWRE43eFpMeGNHSzV3RUoxNndLdTJSbyIsInZlcnNpb24iOjN9",
24 "isText": true,
25 "isEncrypted": false,
26 "signature": "3qFMqF2P1Xy6bAEHmcqKbxMu4zRyVfbGf87mBrBNAcyw9hYpAa3A1dYzMjbBdWMQQNBBugzQHpsy246CrnoYRuoc"
27 },
28 ...
29]

SEARCH_TRANSACTIONS

Equivalent to: /transactions/search?

1const response = await qortalRequest({
2 action: "SEARCH_TRANSACTIONS",
3 // startBlock: 1139000,
4 // blockLimit: 1000,
5 txGroupId: 0,
6 txType: [
7 "PAYMENT",
8 "REWARD_SHARE"
9 ],
10 confirmationStatus: "CONFIRMED",
11 limit: 10,
12 offset: 0,
13 reverse: false
14});

RESPONSE

1[
2
3 {
4 "type": "PAYMENT",
5 "timestamp": 1593450783307,
6 "reference": "TvFaY9C3CPuNwNeE2HthKCbu3BtRhqSNaQ2UuuaobphfErasiVAnKcU8aFyeRiTi8B3rgiNVZMzWWWh48y155Gk",
7 "fee": "0.00100000",
8 "signature": "5kMSsag5nj4YqNrz7kQ3inQ1xjoC6vc2Z8Trhiz2Yi8okShATP43qRkMaFhDUAXpSKDQd8xEXuWvMzXER4BQLFRx",
9 "txGroupId": 0,
10 "blockHeight": 14,
11 "approvalStatus": "NOT_REQUIRED",
12 "creatorAddress": "QfmM8dgfikTB2FYVuJ9owzQXVm8wP7T4QT",
13 "senderPublicKey": "7YfqZ1xWFnDWTThUG1uRYK3CX1vb4mNCH8gvqtH2VCeg",
14 "recipient": "QRFHr4jnVgvAsPTubeSrh8bPy1yzwzYaWD",
15 "amount": "0.01000000"
16 },
17 {
18 "type": "PAYMENT",
19 "timestamp": 1593453009886,
20 "reference": "4FF6ADq3hqsw2HpbhSdr56LvyZj9yfQLVSztKxaEGc8DvMZWZD5c5B3nR7jzC9MzzsNeqMdauV6q7ZnKm6DUqBMm",
21 "fee": "0.00100000",
22 "signature": "5kioTqM1C4gyRnBLEPx76V67bHGuW7t4YEZRZwg3HnKkpHvF2h4vx9uUwRNiJR1NPAEp5GWMDKsAsi7GP9LbozNM",
23 "txGroupId": 0,
24 "blockHeight": 50,
25 "approvalStatus": "NOT_REQUIRED",
26 "creatorAddress": "Qi3N6fNRrs15EHmkxYyWHyh4z3Dp2rVU2i",
27 "senderPublicKey": "HmViWJ2SMRVTYNuMvNYFBX7DitXcEB2gBZasAN3uheJL",
28 "recipient": "QPFM4xX2826MuBEhMtdReW1QR3vRYrQff3",
29 "amount": "1.00000000"
30 },
31 ...
32]

SEND_CHAT_MESSAGE

Send a group chat message

This action requires user approval

1const response = await qortalRequest({
2 action: "SEND_CHAT_MESSAGE",
3 groupId: 0,
4 message: "Test"
5});

RESPONSE

1true

Send a private chat message

1const response = await qortalRequest({
2 action: "SEND_CHAT_MESSAGE",
3 destinationAddress: "QZLJV7wbaFyxaoZQsjm6rb9MWMiDzWsqM2",
4 message: "Test"
5});

RESPONSE

1true

SEND_COIN

Send QORT to address

This action requires user approval

1const response = await qortalRequest({
2 action: "SEND_COIN",
3 coin: "QORT",
4 destinationAddress: "Qi3x7zVhN17mcYm9JTrEYaFihmETSZTzPD",
5 amount: 1.00000000 // 1 QORT
6});

RESPONSE

1{
2 amount: '1.00000000'
3 approvalStatus: 'NOT_REQUIRED'
4 creatorAddress: 'QMjCNsctvWLoDdPSRpHn6TF2j96iDr9YWm'
5 fee: '0.00100000'
6 recipient: 'Qi3x7zVhN17mcYm9JTrEYaFihmETSZTzPD'
7 reference: '26xJXTxcdXhFUYFkyZ7qKkj94RtaLBevcyQgCwK3W5xt7JkGPrCbvNgdC46CmJA65cjTCXMykwiyYJfVsPdsU1fS'
8 senderPublicKey: 'Bjo1iUHJXbCb4LKabmE6KWNL5jSgCK36ypasoDgJG53U'
9 signature: '4j2iPN5Xwgocs8Z32JB4UB63G87qS43kPyEwFmQMLvWBXtrSQwAfyx8S9CqQvbregnstXFKqXpkPT2dNdAscriT4'
10 timestamp: 1684321310522
11 txGroupId: 0
12 type: 'PAYMENT'
13}

Send foreign coin to address

1const response = await qortalRequest({
2 action: "SEND_COIN",
3 coin: "LTC",
4 destinationAddress: "LSdTvMHRm8sScqwCi6x9wzYQae8JeZhx6y",
5 amount: 1.00000000, // 1 LTC
6});

SEND_LOCAL_NOTIFICATION

Send a notification to the user from your Q-App

1const response = await qortalRequest({
2 action: 'SEND_LOCAL_NOTIFICATION',
3 qortalLink: 'qortal://APP/Ear-Bump/liked',
4 title: 'hello testing',
5 url: "qortal://APP/Q-Blog",
6 message: 'this is the body message',
7 icon: "data:image/gif;base64,R0lGODlhPQBEAPeoAJosM//AwO/AwHVYZ/z595kzAP/s7P+goOXMv8+fhw/v739/f+8PD98fH/8mJl+fn/9ZWb8/PzWlwv///6wWGbImAPgTEMImIN9gUFCEm/gDALULDN8PAD6atYdCTX9gUNKlj8wZAKUsAOzZz+UMAOsJAP/Z2ccMDA8PD/95eX5NWvsJCOVNQPtfX/8zM8+QePLl38MGBr8JCP+zs9myn/8GBqwpAP/GxgwJCPny78lzYLgjAJ8vAP9fX/+MjMUcAN8zM/9wcM8ZGcATEL+QePdZWf/29uc/P9cmJu9MTDImIN+/r7+/vz8/P8VNQGNugV8AAF9fX8swMNgTAFlDOICAgPNSUnNWSMQ5MBAQEJE3QPIGAM9AQMqGcG9vb6MhJsEdGM8vLx8fH98AANIWAMuQeL8fABkTEPPQ0OM5OSYdGFl5jo+Pj/+pqcsTE78wMFNGQLYmID4dGPvd3UBAQJmTkP+8vH9QUK+vr8ZWSHpzcJMmILdwcLOGcHRQUHxwcK9PT9DQ0O/v70w5MLypoG8wKOuwsP/g4P/Q0IcwKEswKMl8aJ9fX2xjdOtGRs/Pz+Dg4GImIP8gIH0....."
8})

RESPONSE

1true

SET_PROFILE_DATA

This action requires user approval

Sets a custom property in the user's profile. The custom property will be a key-value pair object that will have a custom property name. There can be more than one key-value pair per object for every custom property.

We call this within a qortalRequestWithTimeout function because we want to wait for the user to navigate and check the auto-filled info in their profile before updating their profile with the new data.

Set a normal custom property

1const summary = await qortalRequestWithTimeout({
2 action: "SET_PROFILE_DATA",
3 property: "foo",
4 data: {
5 customData: {
6 newKey: "bar"
7 }
8 }
9}, 30000);

RESPONSE

1"saved"

The user can had a "-private" suffix to the property name to make it private. This will make the property encrypted, meaning that Q-Apps will require user approval to access that specific property.

Set a private custom property

1const summary = await qortalRequestWithTimeout({
2 action: "SET_PROFILE_DATA",
3 property: "fruits-private",
4 data: {
5 customData: {
6 berries: "strawberries",
7 }
8 }
9}, 30000);

RESPONSE

1"saved"

VOTE_ON_POLL

Vote on a previously created poll. If the poll name doesn't exist, it will throw an error.

This action requires user approval

1await qortalRequest({
2 action: "VOTE_ON_POLL",
3 pollName: "A test poll 3",
4 optionIndex: 1,
5});

RESPONSE

1{
2 "type": "VOTE_ON_POLL",
3 "timestamp": 1697286687406,
4 "reference": "3jU9WpEPAvu9iL3cMfVd2AUmn9AijJRzkGCxVtXfpuUFZubM8AFDcbk5XA9m5AhPfsbMDFkSDzPJnkjeLA5GA59E",
5 "fee": "0.01000000",
6 "signature": "3QJ1EUvX3rskVNaP3RWvJwb9DsGgHPvneWqBWS62PCcuCj5N4Ei9Tr4nFj4nQeMqMU2qNkVD3Sb59e7iUWkawH3s",
7 "txGroupId": 0,
8 "approvalStatus": "NOT_REQUIRED",
9 "creatorAddress": "Qhxphh7g5iNtxAyLLpPMZzp4X85yf2tVam",
10 "voterPublicKey": "C5spuNU1BAHZDEkxF3wnrAPRDuNrVceaDJ6tDKitenko",
11 "pollName": "A test poll 3",
12 "optionIndex": 1
13}
SupportPrivacy Policy