Introduction
Welcome to The Codex API documentation! Among other things, you can use our API to create Codex Records on the Ethereum Blockchain without the need for any direct blockchain interaction.
To use The Codex API, you will need to register an OAuth2 application with us. See Authentication for more information.
If you have any questions or suggestions for edits to this documentation, please visit the GitHub repository and open an issue and/or pull request.
API URLs
The Codex API is available in 3 environments that correspond to 3 different Ethereum networks, so the API URL will vary based on the environment you are developing against.
Environment | Network | URL | Description |
---|---|---|---|
Development | Ropsten | http://ropsten-api.codexprotocol.com | We frequently deploy (possibly breaking) changes here. It is not recommended to develop your application against this network unless you are looking to test the latest changes to the Codex Protocol. |
Staging | Rinkeby | https://rinkeby-api.codexprotocol.com | We only deploy stable builds here, prior to Mainnet. This is the recommended network to test your application before deploying to production. |
Production | Mainnet | https://api.codexprotocol.com | Changes only get deployed here after being validated in our development and staging environments. All Codex Records created here are immutably recorded on the blockchain and cannot be destroyed. Integration into Mainnet should only be done after ensuring your application runs smoothly in the staging environment. |
Authentication
Registering an Application
Currently there is no admin interface built out for application developers, so the process for registering an application requires manual steps from both Codex and the application developer. To register an application, send us an email with the following information:
Property | Description |
---|---|
name | The name of the application. This will be shown in The Codex Viewer as a registered application, taking the place of your application's Ethereum address in the Codex Record’s provenance. |
The application developer’s email address. This will be used to communicate any breaking API changes or development updates. | |
webhookUrl | For example, https://your-api.example.com/codex-webhook . Since blockchain transactions are asynchronous, this webhookUrl will be used to inform your application that an event has occurred, for example when your Codex Records have been created. See webhooks for details. |
Access Tokens
Obtaining an Access Token
Before you can make API calls, you'll need an access token. See Get an Access Token for more details.
Access Token Expiration
Example response when an expired access token is provided:
{
"error": {
"code": 401,
"message": "Invalid token: access token is invalid"
},
"result": null
}
For security purposes, access tokens will expire after a certain period of time. If you send a request and and receive a 401 Unauthorized error, you'll need to generate another access token and send the request again.
The following table shows the access token expiries for each environment:
Network | Expiry |
---|---|
Ropsten | 30 days after creation |
Rinkeby | 90 days after creation |
Mainnet | 90 days after creation |
Making Authenticated Requests
Making an authenticated API call:
import request from 'request'
// retrieve a list of your application's Codex Records
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result) // an array of Codex Records
})
Remember to replace the example
accessToken
with your personalaccessToken
.
The Codex API expects an OAuth2 access token to be included with all requests made to the server in the Authorization header as follows:
Authorization: Bearer d49694e5a3459759cc7ac1741de246e184e51d6e
Public Routes
Unlike client routes, public routes do not require an
accessToken
in the Authorization header. However, sometimes an accessToken
is required to retrieve a full response.
For example, to retrieve public information about a Codex Record, you could call
GET /v1/record/:tokenId
without an accessToken
. If the Codex Record is
public, then you will receive a full response. However, if the Codex Record is
private, calling this route without an accessToken
(or an accessToken
that
doesn't belong to the owner of the Codex Record) will return a response with all
private information removed (i.e. no metadata
).
Similarly, you will not be able to call public routes such as GET /v1/record/:tokenId/metadata
if the record is private and the accessToken
is missing (or if you provide
your accessToken
but you are not the owner.)
Get an Access Token
This endpoint can be used to generate an access token, which is required for making authenticated requests.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/oauth2/token',
method: 'post',
json: true,
// by using "form" here instead of "body", request will send this request as
// application/x-www-form-urlencoded
form: {
grant_type: 'client_credentials',
client_id: '5bae56e05d7971becf854a70',
client_secret: '352db118-b76c-4c60-9ed8-aa13cb4621b8',
},
}
request(options, (error, response) => {
console.log(response.body.result.accessToken) // also available is
})
The above API call returns an OAuth2 Access Token.
Remember to replace the example
client_id
andclient_secret
with your application'sclient_id
andclient_secret
.
HTTP Request
GET /v1/oauth2/token
Request Parameters
Parameter | Type | Description |
---|---|---|
client_id | String | The unique identifier for your application, provided by Codex. |
grant_type | String | Must be client_credentials . |
client_secret | String | Your application's secret, provided by Codex. |
Get a Codex Record
This endpoint can be used to retrieve a specific Codex Record. This will return a full Codex Record document, including it's metadata and provenance.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body.result) // Codex Record with tokenId 0
})
The above API call returns a single Codex Record.
HTTP Request
GET /v1/records/:tokenId
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record to retrieve. |
Get a Codex Record's Metadata Only
This endpoint can be used to retrieve only the metadata of a specific Codex Record. This is useful if you do not need the entire Codex Record document.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0/metadata',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body.result) // the metadata of Codex Record with tokenId 0
})
The above API call returns a Codex Record's metadata.
HTTP Request
GET /v1/records/:tokenId/metadata
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the metadata of. |
Get a Codex Record's Provenance Only
This endpoint can be used to retrieve only the provenance of a specific Codex Record. This is useful if you do not need the entire Codex Record document.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0/provenance',
method: 'get',
json: true,
// sort by date created
body: {
order: 'createdAt',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the provenance of Codex Record with tokenId 0
})
The above API call returns a Codex Record's provenance.
HTTP Request
GET /v1/records/:tokenId/provenance
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the provenance of. |
Request Parameters
Parameter | Type | Default | Description |
---|---|---|---|
order | String | -createdAt | To sort in chronological order, specify this value as createdAt . |
Get a Codex Record's Main Image Only
This endpoint can be used to retrieve only the main image for a specific Codex Record. This is useful if you do not need the entire Codex Record document.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0/main-image',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body.result) // the mainImage of Codex Record with tokenId 0
})
The above API call returns a single File.
HTTP Request
GET /v1/records/:tokenId/main-image
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the main image of. |
Get a Codex Record's Images Only
This endpoint can be used to retrieve only the images
array for a specific
Codex Record. This is useful if you do not need the entire Codex Record document.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0/images',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body.result) // the images array of Codex Record with tokenId 0
})
The above API call returns an array of Files.
HTTP Request
GET /v1/records/:tokenId/images
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the images array of. |
Get a Codex Record's Files Only
This endpoint can be used to retrieve only the files
array for a specific
Codex Record. This is useful if you do not need the entire Codex Record document.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0/files',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body.result) // the files array of Codex Record with tokenId 0
})
The above API call returns an array of Files.
HTTP Request
GET /v1/records/:tokenId/files
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the files array of. |
Client Routes
Unlike public routes, client routes are all prefixed with
/v1/client
and only perform actions on data owned by your application. All
client routes require an accessToken
in the Authorization header, as described
in the Access Tokens section.
Get Client
This endpoint can be used to retrieve the details of your application. This is useful if you want to check your CODX balance before sending a request that would consume CODX.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result) // your Client record
})
The above API call returns a single Client.
HTTP Request
GET /v1/client
Get All Codex Records
This endpoint can be used to retrieve all Codex Records that your application currently owns.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// sort by date created in reverse and get 5 records, skipping the first ten
body: {
limit: 5,
offset: 10,
order: '-createdAt',
},
}
request(options, (error, response) => {
console.log(response.body.result) // an array of your application's Codex Records
})
The above API call returns an array of Codex Records.
HTTP Request
GET /v1/client/records
Request Parameters
Parameter | Type | Default | Description |
---|---|---|---|
limit | Number | 100 | How many records to retrieve. Use with offset to paginate through Codex Records. |
offset | Number | 0 | How many records to skip before applying the limit . Use with limit to paginate through Codex Records. |
order | String | createdAt | To sort in reverse order, specify this value as -createdAt . Other options are metadata.name and -metadata.name . |
Create a Codex Record
This endpoint can be used to begin the creation of a Codex Record. With this request, you're actually creating the Codex Record's metadata since the Codex Record itself can't be created until the transaction has been mined on the blockchain.
import fs from 'fs'
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/record',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// by using "formData" here instead of "body", request will send this request
// as multipart/form-data, which is necessary for sending file data
formData: {
name: 'Really Cool Codex Record',
description: 'This is a really cool Codex Record!',
mainImage: fs.createReadStream('/tmp/uploads/cool-main-image.jpg'),
images: [
fs.createReadStream('/tmp/uploads/cool-supplemental-image-1.jpg'),
fs.createReadStream('/tmp/uploads/cool-supplemental-image-2.jpg'),
],
files: [
fs.createReadStream('/tmp/uploads/author-notes.txt'),
fs.createReadStream('/tmp/uploads/certificate-of-authenticity.pdf'),
],
isPrivate: false,
isHistoricalProvenancePrivate: true,
whitelistedEmails: [
'user@example.com',
],
whitelistedAddresses: [
'0xf17f52151ebef6c7334fad080c5704d77216b732',
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
],
},
}
request(options, (error, response) => {
console.log(response.body.result) // the metadata for the soon-to-be-created Codex Record
})
The above API call returns Codex Record metadata, since the Codex Record itself can't be created until the transaction has been mined.
HTTP Request
POST /v1/client/record
Webhook Event
Event Name | Recipient |
---|---|
codex-record:created |
Owner |
Request Parameters
Parameter | Type | Description |
---|---|---|
name | String | The plain text name of this Codex Record. |
description | String | (Optional) The plain text description of this Codex Record. The field can be set to null or simply omitted to leave the description empty. |
mainImage | File Data | The main image of this Codex Record. |
images | Array[File Data] | (Optional) An array of supplemental images that belong to this metadata. |
files | Array[File Data] | (Optional) An array of supplemental files that belong to this metadata. This is considered the "historical provenance" of the Codex Record. |
additionalMetadata | Object | (Optional) A list of additional metadata about this asset. See Additional Metadata for details. |
auctionHouseMetadata | Object | (Optional) An arbitrary list of data specific to your application (this field is ignored if your application is not an auction house.) See Auction House Metadata for details. |
isPrivate | Boolean | (Optional, default true ) This flag indicates that the metadata for the Codex Record is private and can only be retrieved by the owner, the approvedAddress , and the addresses listed in whitelistedAddresses / whitelistedEmails . |
isHistoricalProvenancePrivate | Boolean | (Optional, default true ) This flag indicates whether or not historical provenance (i.e. files ) should be hidden, regardless of the value of isPrivate . |
whitelistedEmails | Array[String] | (Optional) An array of email addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. |
whitelistedAddresses | Array[String] | (Optional) An array of Ethereum addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. |
Modify a Codex Record
This endpoint can be used to modify the "off-chain" data for a Codex Record. Since no "on-chain" data can be modified with this request, the response will be the immediately-updated Codex Record.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/record/0',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
isPrivate: false,
isHistoricalProvenancePrivate: false,
whitelistedEmails: [
'user@example.com',
],
whitelistedAddresses: [
'0xf17f52151ebef6c7334fad080c5704d77216b732',
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
],
},
}
request(options, (error, response) => {
console.log(response.body.result) // the immediately-updated Codex Record
})
The above API call returns the immediately-updated Codex Record.
HTTP Request
PUT /v1/client/record/:tokenId
Webhook Event
Event Name | Recipient |
---|---|
codex-record:address-whitelisted (if whitelistedAddresses and/or whitelistedEmails was replaced) |
Each newly added whitelisted user |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record to update. |
Request Parameters
At least one of the following parameters is required for this route:
Parameter | Type | Description |
---|---|---|
isPrivate | Boolean | This flag indicates that the metadata for the Codex Record is private and can only be retrieved by the owner, the approvedAddress , and the addresses listed in whitelistedAddresses / whitelistedEmails . |
whitelistedEmails | Array[String] | An array of email addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. |
whitelistedAddresses | Array[String] | An array of Ethereum addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. |
isHistoricalProvenancePrivate | Boolean | This flag indicates whether or not historical provenance (i.e. metadata.files ) should be hidden, regardless of the value of isPrivate . |
Modify a Codex Record's Metadata
This endpoint can be used to modify the "on-chain" data for a Codex Record. With this request, you're actually creating a Pending Update since the metadata itself can't be modified until the transaction has been mined on the blockchain.
import fs from 'fs'
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/record/0/metadata',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// by using "formData" here instead of "body", request will send this request
// as multipart/form-data, which is necessary for sending file data
formData: {
name: 'Super Duper New Title',
description: 'This will replace the current description!',
// replace the current mainImage
newMainImage: fs.createReadStream('/tmp/uploads/super-duper-new-main-image.jpg'),
// add these files to the files & images arrays
newFiles: [
fs.createReadStream('/tmp/uploads/2018-appraisal-documents.pdf'),
],
newImages: [
fs.createReadStream('/tmp/uploads/super-duper-new-photo-1.jpg'),
fs.createReadStream('/tmp/uploads/super-duper-new-photo-2.jpg'),
],
// before adding new files & images, replace the existing arrays with these
// see "Removing Files and Images" for details
files: [
{ id: '5bb640d492d258fdf2efa290', },
{ id: '5bb640d492d258fdf2efa291', },
],
images: [
{ id: '5bb640d492d258fdf2efa292', },
{ id: '5bb640d492d258fdf2efa293', },
],
},
}
request(options, (error, response) => {
console.log(response.body.result) // a new Pending Update
})
The above API call returns a new Pending Update.
HTTP Request
PUT /v1/client/record/:tokenId/metadata
Webhook Event
Event Name | Recipient |
---|---|
codex-record:modified |
Owner |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to modify the metadata of. |
Request Parameters
At least one of the following parameters is required for this route:
Parameter | Type | Description |
---|---|---|
name | String | The new name of this Codex Record. |
files | Array[File] | See Removing Files and Images for details. |
images | Array[File] | See Removing Files and Images for details. |
newFiles | Array[File Data] | An array of supplemental files to append to the existing files array for this Codex Record. |
newImages | Array[File Data] | An array of supplemental images to append to the existing images array for this Codex Record. |
description | String | The new description of this Codex Record. The field can be set to null to erase an existing description. |
newMainImage | File Data | The new main image of this Codex Record. |
Removing Files and Images
Removing the File with id
5bb640d492d258fdf2efa294
from a Codex Record'simages
array:
import request from 'request'
// first, get the existing images
const getCodexRecordOptions = {
url: 'https://rinkeby-api.codexprotocol.com/v1/records/0/images',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(getCodexRecordOptions, (getCodexRecordError, getCodexRecordResponse) => {
const codexRecordImages = getCodexRecordResponse.body.result
// the remove the image with id '5bb640d492d258fdf2efa294'
const newCodexRecordImages = codexRecordImages
.filter((image) => {
return image.id !== '5bb640d492d258fdf2efa294'
})
// this map is optional since the the only field you're required to send in
// is the File's id
//
// it's perfectly fine to send the entire File object, but it's a good idea
// to reduce the amount of unnecessary data sent with requests
.map((image) => {
return { id: image.id }
})
// now send in the "new state" of the images array
const updateCodexRecordOptions = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/record/0/metadata',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
images: newCodexRecordImages,
},
}
request(updateCodexRecordOptions, (updateCodexRecordError, updateCodexRecordResponse) => {
console.log(updateCodexRecordResponse.body.result) // a new Pending Update
})
})
The files
and images
parameters are each arrays of File objects
(not to be confused with binary "File Data" in a multipart/form-data
request...)
These parameters provide the mechanism for removing files and/or images.
To remove files and/or images from a Codex Record, follow these steps:
If you do not already have the Codex Record, you will need to retrieve the existing files and/or images first. You can get this data by getting the whole Codex Record, getting the Codex Record's files only, and/or getting the Codex Record's images only.
Remove the images from the array(s).
Send the "new state" of the array(s) with this request as
files
and/orimages
.
Start a Transfer
This endpoint can be used to start the process of transferring a Codex Record to another person. Transferring is a two step process, see Transferring Codex Records for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/transfer/approve',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// address and email are mutually exclusive, choose one or the other but not both
body: {
// email: 'email@example.com',
address: '0xf17f52151ebef6c7334fad080c5704d77216b732',
},
}
request(options, (error, response) => {
console.log(response.body.result)
})
The above API call returns the Codex Record being transferred, although no data will have changed at this point.
HTTP Request
PUT /v1/client/records/:tokenId/transfer/approve
Webhook Event
Event Name | Recipient |
---|---|
codex-record:address-approved:owner |
Owner |
codex-record:address-approved:approved |
The newly approved user |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record to transfer. |
Request Parameters
Parameter | Type | Description |
---|---|---|
String | (Required if address is not specified) The email address of the person to transfer this record to. |
|
address | String | (Required if email is not specified) The Ethereum address of the person to transfer this record to. |
Cancel a Transfer
This endpoint can be used to stop the process of transferring a Codex Record to
another person. This essentially removes the currently set approvedAddress
,
preventing that address from accepting the transfer (and removing it from their
incoming transfers list.) See
Transferring Codex Records for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/transfer/cancel',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result)
})
The above API call returns the Codex Record for which the transfer is being cancelled, although no data will have changed at this point.
HTTP Request
PUT /v1/client/records/:tokenId/transfer/cancel
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to cancel the transfer of. |
Accept a Transfer
This endpoint can be used to accept an incoming transfer of a Codex Record. See Transferring Codex Records for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/transfer/accept',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result)
})
The above API call returns the Codex Record for which the transfer is being accepted, although no data will have changed at this point.
HTTP Request
PUT /v1/client/records/:tokenId/transfer/accept
Webhook Event
Event Name | Recipient |
---|---|
codex-record:transferred:old-owner |
The old owner |
codex-record:transferred:new-owner |
The new owner |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to accept the transfer of. |
Ignore a Transfer
This endpoint can be used to mark an incoming transfer as "ignored". This is useful to (visually) remove a transfer from your incoming transfers list without accepting it, because there's no blockchain mechanism to explicitly reject a transfer. See Transferring Codex Records for details.
This route sets the Codex Record's isIgnored
property to true
. See Codex
Record for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/transfer/ignore',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the immediately-updated Codex Record
})
The above API call returns the immediately-updated Codex Record, with
isIgnored
set totrue
.
HTTP Request
PUT /v1/client/records/:tokenId/transfer/ignore
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to ignore the transfer of. |
Get Outgoing Transfers
This endpoint can be used to retrieve a list of your application's outgoing
transfers. Outgoing transfers are Codex Records that your application is owner
of, which also have an approvedAddress
set. See Transferring Codex Records
for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/transfers/outgoing',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// sort by date created in reverse and get 5 records, skipping the first ten
body: {
limit: 5,
offset: 10,
order: '-createdAt',
},
}
request(options, (error, response) => {
console.log(response.body.result) // your application's outgoing transfers
})
The above API call returns an array of Codex Records.
HTTP Request
GET /v1/client/transfers/outgoing
Request Parameters
Parameter | Type | Default | Description |
---|---|---|---|
limit | Number | 100 | How many records to retrieve. Use with offset to paginate through outgoing transfers. |
offset | Number | 0 | How many records to skip before applying the limit . Use with limit to paginate through outgoing transfers. |
order | String | createdAt | To sort in reverse order, specify this value as -createdAt . |
Get Incoming Transfers
This endpoint can be used to retrieve a list of your application's incoming
transfers. Incoming transfers are Codex Records that your application does not
own, which also have their approvedAddress
property set to your application's
Ethereum address. See Transferring Codex Records
for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/transfers/incoming',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// sort by date created in reverse and get 5 records, skipping the first ten
body: {
limit: 5,
offset: 10,
order: '-createdAt',
// don't return ignored transfers
filters: {
isIgnored: false,
},
},
}
request(options, (error, response) => {
console.log(response.body.result) // your application's incoming transfers
})
The above API call returns an array of Codex Records.
HTTP Request
GET /v1/client/transfers/incoming
Request Parameters
Parameter | Type | Default | Description |
---|---|---|---|
limit | Number | 100 | How many records to retrieve. Use with offset to paginate through incoming transfers. |
offset | Number | 0 | How many records to skip before applying the limit . Use with limit to paginate through incoming transfers. |
order | String | createdAt | To sort in reverse order, specify this value as -createdAt . |
filters[isIgnored] | Boolean | This can be used to return only ignored transfers (when true ), or only non-ignored transfers (when false ). |
Get a Codex Record's Whitelisted Addresses
This endpoint can be used to retrieve a Codex Record's whitelistedAddresses
property, an array of Ethereum addresses allowed to view private metadata for
this Codex Record. See Whitelisted Addresses for
details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-addresses',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the whitelistedAddresses array for this Codex Record
})
The above API call returns an array of Ethereum addresses (strings).
HTTP Request
GET /v1/client/records/:tokenId/whitelisted-addresses
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted addresses of. |
Add an Address to a Codex Record's Whitelisted Addresses
This endpoint can be used to add a single Ethereum address to a Codex Record's
whitelistedAddresses
array. The address is then allowed to view private
metadata for this Codex Record, effectively giving them read-only access. See
Whitelisted Addresses for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-addresses',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
address: '0xf17f52151ebef6c7334fad080c5704d77216b732',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the updated whitelistedAddresses array for this Codex Record
})
The above API call returns the updated
whitelistedAddresses
array.
HTTP Request
POST /v1/client/records/:tokenId/whitelisted-addresses
Webhook Event
Event Name | Recipient |
---|---|
codex-record:address-whitelisted |
The newly whitelisted user |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted addresses of. |
Request Parameters
Parameter | Type | Description |
---|---|---|
address | String | The Ethereum address to add to the list of whitelisted addresses. |
Remove an Address from a Codex Record's Whitelisted Addresses
This endpoint can be used to remove a single Ethereum address from a Codex
Record's whitelistedAddresses
array, revoking their ability to view the
private metadata. See Whitelisted Addresses for
details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-addresses',
method: 'delete',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
address: '0xf17f52151ebef6c7334fad080c5704d77216b732',
},
}
request(options, (error, response) => {
// no response bodies for DELETE requests
})
The above API call returns no response body, but a 204 status code will be returned to indicate a successful deletion.
HTTP Request
DELETE /v1/client/records/:tokenId/whitelisted-addresses
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted addresses of. |
Request Parameters
Parameter | Type | Description |
---|---|---|
address | String | The Ethereum address to remove from the list of whitelisted addresses. |
Entirely Replace a Codex Record's Whitelisted Addresses
This endpoint can be used to entirely replace a Codex Record's
whitelistedAddresses
array, instead of adding and removing one-by-one.
See Whitelisted Addresses for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-addresses',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
addresses: [
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
'0x2191ef87e392377ec08e7c08eb105ef5448eced5',
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
],
},
}
request(options, (error, response) => {
console.log(response.body.result) // the updated whitelistedAddresses array for this Codex Record
})
The above API call returns an array of Ethereum addresses (strings).
HTTP Request
PUT /v1/client/records/:tokenId/whitelisted-addresses
Webhook Event
Event Name | Recipient |
---|---|
codex-record:address-whitelisted |
Each newly whitelisted user |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted addresses of. |
Request Parameters
Parameter | Type | Description |
---|---|---|
addresses | Array[String] | An array of Ethereum addresses to replace the current whitelisted addresses with. |
Get a Codex Record's Whitelisted Emails
This endpoint can be used to retrieve a Codex Record's whitelistedEmails
property, an array of email addresses allowed to view private metadata for
this Codex Record. See Whitelisted Addresses for
details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-emails',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the whitelistedEmails array for this Codex Record
})
The above API call returns an array of email addresses (strings).
HTTP Request
GET /v1/client/records/:tokenId/whitelisted-emails
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted emails of. |
Add an Email to a Codex Record's Whitelisted Emails
This endpoint can be used to add a single email address to a Codex Record's
whitelistedEmails
array. The address is then allowed to view private metadata
for this Codex Record, effectively giving them read-only access. See
Whitelisted Addresses for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-emails',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
email: '0xf17f52151ebef6c7334fad080c5704d77216b732',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the updated whitelistedEmails array for this Codex Record
})
The above API call returns the updated
whitelistedEmails
array.
HTTP Request
POST /v1/client/records/:tokenId/whitelisted-emails
Webhook Event
Event Name | Recipient |
---|---|
codex-record:address-whitelisted |
The newly whitelisted user |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted emails of. |
Request Parameters
Parameter | Type | Description |
---|---|---|
String | The email address to add to the list of whitelisted emails. |
Remove an Email from a Codex Record's Whitelisted Emails
This endpoint can be used to remove a single email address from a Codex
Record's whitelistedEmails
array, revoking their ability to view the
private metadata. See Whitelisted Addresses for
details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-emails',
method: 'delete',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
email: '0xf17f52151ebef6c7334fad080c5704d77216b732',
},
}
request(options, (error, response) => {
// no response bodies for DELETE requests
})
The above API call returns no response body, but a 204 status code will be returned to indicate a successful deletion.
HTTP Request
DELETE /v1/client/records/:tokenId/whitelisted-emails
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted emails of. |
Request Parameters
Parameter | Type | Description |
---|---|---|
String | The email address to remove from the list of whitelisted emails. |
Entirely Replace a Codex Record's Whitelisted Emails
This endpoint can be used to entirely replace a Codex Record's
whitelistedEmails
array, instead of adding and removing one-by-one.
See Whitelisted Addresses for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/records/0/whitelisted-emails',
method: 'put',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
addresses: [
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
'0x2191ef87e392377ec08e7c08eb105ef5448eced5',
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
],
},
}
request(options, (error, response) => {
console.log(response.body.result) // the updated whitelistedEmails array for this Codex Record
})
The above API call returns an array of email addresses (strings).
HTTP Request
PUT /v1/client/records/:tokenId/whitelisted-emails
Webhook Event
Event Name | Recipient |
---|---|
codex-record:address-whitelisted |
Each newly whitelisted user |
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the whitelisted emails of. |
Request Parameters
Parameter | Type | Description |
---|---|---|
addresses | Array[String] | An array of email addresses to replace the current whitelisted emails with. |
Request Faucet Drip
This endpoint can be used to request CODX from the faucet in Testnets. See CODX Tokens & Fees for details.
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/faucet/drip',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
// nothing returned by this route
})
The above API call returns nothing, but a 200 response code means the request was successful
HTTP Request
PUT /v1/client/faucet/drip
Webhook Event
Event Name | Recipient |
---|---|
codex-coin:transferred |
Drip requester (your application) |
Bulk Routes
Bulk routes are a subset of client routes, which means they
are also prefixed with /v1/client
and only perform actions on data owned by
your application. All bulk routes require an accessToken
in the Authorization
header, as described in the Access Tokens section.
However, bulk routes are listed separately here because they work a bit differently. They are designed to take large sets of data and process actions in a queue. For example, these bulk routes can be used to create hundreds of Codex Records with just one API request.
Create Codex Records in Bulk
This endpoint can be used to create a large number of Codex Records with just one API request. This route is essentially the same as the single-record creation route, but it takes an array of metadata objects instead of a single metadata object. Another difference is that this route takes URLs for files and NOT file data (these files are downloaded an re-hosted by Codex during processing.)
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/bulk-transaction/record-mint',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
generateClaimCode: true,
proxyUserAddress: '0xf17f52151ebef6c7334fad080c5704d77216b732'
metadata: [
{
name: 'Really Cool Codex Record #1',
description: 'This is a really cool Codex Record!',
mainImageUrl: 'https://example.com/images/cool-main-image.jpg',
imageUrls: [
'https://example.com/images/cool-supplemental-image-1.jpg',
'https://example.com/images/cool-supplemental-image-2.jpg',
],
fileUrls: [
'https://example.com/files/author-notes.txt',
'https://example.com/files/certificate-of-authenticity.pdf',
],
isPrivate: true,
isHistoricalProvenancePrivate: true,
whitelistedEmails: [
'user-1@example.com',
],
whitelistedAddresses: [
'0xf17f52151ebef6c7334fad080c5704d77216b732',
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
],
additionalMetadata: {
lotNumber: '123A',
salePrice: '1000',
saleCurrency: 'USD',
editionNumber: '16'
totalEditions: '64'
condition: 'Excellent',
dimensionsInches: '18 x 23',
creatorName: 'Cool Guy McGee',
auctionName: 'My Cool Auction',
medium: 'mixed media on paper',
signatureLocation: 'front, top left',
dimensionsCentimeters: '45.7 x 58.4',
assetSoldAt: '2019-03-21T16:30:06.030Z',
assetCreatedAt: '1993-01-01T00:00:00.000Z',
},
auctionHouseMetadata: {
id: '1234',
linkbackUrl: 'https://example.com/my-auction-house/asset/1234',
// any other arbitrary fields
transactionId: 27299,
inventoryNumber: 'TWC1123-G16926-001',
},
},
{
name: 'Really Cool Codex Record #2',
description: 'This is also a really cool Codex Record!',
mainImageUrl: 'https://example.com/images/another-cool-main-image.jpg',
imageUrls: [
'https://example.com/images/another-cool-supplemental-image-1.jpg',
'https://example.com/images/another-cool-supplemental-image-2.jpg',
],
fileUrls: [
'https://example.com/files/more-author-notes.txt',
'https://example.com/files/another-certificate-of-authenticity.pdf',
],
isPrivate: false,
isHistoricalProvenancePrivate: false,
whitelistedEmails: [
'user-2@example.com',
],
whitelistedAddresses: [
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
],
additionalMetadata: {
lotNumber: '123A',
condition: 'Poor',
editionNumber: '16'
totalEditions: '64'
salePrice: '2000',
saleCurrency: 'USD',
medium: 'linocut in 4 colors',
creatorName: 'Cool Guy McGee',
auctionName: 'My Cool Auction',
dimensionsInches: '7 1/2 x 11 3/4',
dimensionsCentimeters: '19.1 x 29.8',
signatureLocation: 'back, bottom left',
assetSoldAt: '2019-03-20T14:30:06.030Z',
assetCreatedAt: '1976-01-01T00:00:00.000Z',
},
auctionHouseMetadata: {
id: '4321',
linkbackUrl: 'https://example.com/my-auction-house/asset/4321',
// any other arbitrary fields
transactionId: 27300,
inventoryNumber: 'TWC1123-G16926-002',
},
},
// more metadata objects here
],
},
}
request(options, (error, response) => {
console.log(response.body.result) // a Bulk Transaction object
})
The above API call (eventually) creates two Codex Records and returns a single Bulk Transaction.
HTTP Request
POST /v1/client/bulk-transaction/record-mint
Webhook Events
Event Name | Recipient |
---|---|
bulk-transaction:started |
Bulk Transaction creator (and not the proxy user, if specified) |
bulk-transaction:completed |
Bulk Transaction creator (and not the proxy user, if specified) |
Request Parameters
Parameter | Type | Description |
---|---|---|
proxyUserAddress | String | (Optional) The Ethereum address of the user to create these Codex Records for. This field is only for applications with Proxy Users. |
generateClaimCode | Boolean | (Optional, default false ) Generate a claimCode (and claimCodeUrl ) that can subsequently be used users of your application to claim the resulting Bulk Transaction, automatically transferring the Codex Records to their account. |
metadata[][name] | String | The plain text name of this Codex Record. |
metadata[][description] | String | (Optional) The plain text description of this Codex Record. The field can be set to null or simply omitted to leave the description empty. |
metadata[][mainImageUrl] | String | The main image of this Codex Record. |
metadata[][imageUrls] | String | (Optional) An array of supplemental images that belong to this metadata. |
metadata[][fileUrls] | String | (Optional) An array of supplemental files that belong to this metadata. This is considered the "historical provenance" of the Codex Record. |
metadata[][additionalMetadata] | Object | (Optional) A list of additional metadata about this asset. See Additional Metadata for details. |
metadata[][auctionHouseMetadata] | Object | An arbitrary list of data specific to your application (this field is ignored if your application is not an auction house.) IF your account is an auction house account, this field is required (as well as the auctionHouseMetadata.id parameter.) See Auction House Metadata for details. |
metadata[][isPrivate] | Boolean | (Optional, default false ) This flag indicates that the metadata for the Codex Record is private and can only be retrieved by the owner, the approvedAddress , and the addresses listed in whitelistedAddresses / whitelistedEmails . |
metadata[][isHistoricalProvenancePrivate] | Boolean | (Optional, default true ) This flag indicates whether or not historical provenance (i.e. metadata[][files] ) should be hidden, regardless of the value of isPrivate . |
metadata[][whitelistedEmails] | Array[String] | (Optional) An array of email addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. |
metadata[][whitelistedAddresses] | Array[String] | (Optional) An array of Ethereum addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. |
Convert Metadata XML into JSON
It may be easier for your application to export metadata as XML instead of JSON,
so a utility route exists to simply convert an XML file into a JSON response.
You can then use this JSON response as the metadata
array when
creating Codex Records in bulk.
import fs from 'fs'
import request from 'request'
const convertXMLRequestOptions = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/bulk-transactions/record-mint/convert/xml',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// by using "formData" here instead of "body", request will send this request
// as multipart/form-data, which is necessary for sending file data
formData: {
mainImage: fs.createReadStream('/tmp/xml-metadata.xml'),
},
}
request(convertXMLRequestOptions, (convertXMLError, convertXMLResponse) => {
// now take the JSON metadata and send it to the bulk transaction route...
const metadata = convertXMLResponse.body.result
const createBulkTransactionRequestOptions = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/bulk-transaction/record-mint',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
body: {
metadata,
},
}
request(createBulkTransactionRequestOptions, (createBulkTransactionError, createBulkTransaction) => {
console.log(createBulkTransaction.body.result) // a Bulk Transaction object
})
})
The above API sends an XML file, retrieves converted JSON in the response, and sends that JSON metadata to the create Codex Records in Bulk route.
HTTP Request
POST /v1/client/bulk-transactions/record-mint/convert/xml
Request Parameters
Parameter | Type | Description |
---|---|---|
metadata | File Data | The XML file to convert. |
Example XML Structure
<Records>
<Record>
<!-- required -->
<Name>Really Cool Codex Record #1</Name>
<Description>This is a really cool Codex Record!</Description>
<MainImageUrl>https://example.com/images/cool-main-image.jpg</MainImageUrl>
<!-- optional additional images -->
<ImageUrl>https://example.com/images/cool-supplemental-image-1.jpg</ImageUrl>
<ImageUrl>https://example.com/images/cool-supplemental-image-2.jpg</ImageUrl>
<!-- optional additional files -->
<FileUrl>https://example.com/files/author-notes.txt</FileUrl>
<FileUrl>https://example.com/files/certificate-of-authenticity.pdf</FileUrl>
<IsPrivate>false</IsPrivate>
<IsHistoricalProvenancePrivate>false</IsHistoricalProvenancePrivate>
<!-- optional -->
<AdditionalMetadata>
<LotNumber>123A</LotNumber>
<SalePrice>1000</SalePrice>
<SaleCurrency>USD</SaleCurrency>
<Condition>Excellent</Condition>
<EditionNumber>16</EditionNumber>
<TotalEditions>64</TotalEditions>
<Medium>mixed media on paper</Medium>
<CreatorName>Cool Guy McGee</CreatorName>
<AuctionName>My Cool Auction</AuctionName>
<DimensionsInches>18 x 23</DimensionsInches>
<AssetSoldAt>2019-03-21T16:30:06.030Z</AssetSoldAt>
<SignatureLocation>front, top left</SignatureLocation>
<AssetCreatedAt>1993-01-01T00:00:00.000Z</AssetCreatedAt>
<DimensionsCentimeters>45.7 x 58.4</DimensionsCentimeters>
</AdditionalMetadata>
<AuctionHouseMetadata>
<!-- required -->
<Id>1234</Id>
<!-- optional -->
<LinkbackUrl>https://www.heffel.com/auction/Details_E.aspx?ID=30510</LinkbackUrl>
<!-- any other arbitrary fields -->
<TransactionID>27299</TransactionID>
<InventoryNumber>TWC1123-G16926-001</InventoryNumber>
</AuctionHouseMetadata>
</Record>
<!-- more <Record> elements here -->
</Records>
The structure of XML metadata is very similar to the structure of JSON metadata. Please see the example in this section to guide you when constructing your XML metadata.
Get a Bulk Transaction
This endpoint can be used to retrieve a specific Bulk Transaction that your application has created. This data can be used to monitor the progress of an in-progress Bulk Transaction, or to retrieve stats about an already-completed Bulk Transaction (such as how many Codex Records were created.)
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/bulk-transactions/5c8fe94329de5e47fb9df266',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body.result) // Bulk Transaction with id 5c8fe94329de5e47fb9df266
})
The above API call returns a single Bulk Transaction.
HTTP Request
GET /v1/client/bulk-transactions/:bulkTransactionId
URL Parameters
Parameter | Type | Description |
---|---|---|
bulkTransactionId | String | The id of the Bulk Transaction to retrieve. |
Get All Bulk Transactions
This endpoint can be used to retrieve all Bulk Transactions that your application has created. This data can be used to monitor the progress of in-progress Bulk Transactions, or to retrieve stats about a already-completed Bulk Transactions (such as how many Codex Records were created.)
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/bulk-transactions',
method: 'get',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
// sort by date created in reverse and get 5 records, skipping the first ten
body: {
limit: 5,
offset: 10,
order: '-createdAt',
},
}
request(options, (error, response) => {
console.log(response.body.result) // an array of your application's Bulk Transactions
})
The above API call returns an array of Bulk Transactions.
HTTP Request
GET /v1/client/bulk-transactions
Request Parameters
Parameter | Type | Default | Description |
---|---|---|---|
limit | Number | 100 | How many records to retrieve. Use with offset to paginate through Bulk Transactions. |
offset | Number | 0 | How many records to skip before applying the limit . Use with limit to paginate through Bulk Transactions. |
order | String | -createdAt | To sort in chronological order, specify this value as createdAt . |
Claim a Bulk Transaction
If your application creates Codex Records with the intention of transferring
them to your users, it is possible to automate this by
creating Codex Records in bulk with the
generateClaimCode
boolean set to true
. This will cause the resulting Bulk
Transaction to have a claimCode
(and claimCodeUrl
) that can be passed on to
one of your users.
Visiting the claimCodeUrl
will present the user with a summary of the Bulk
Transaction and provide a way for them to create a new Codex Viewer account (or
log into an existing account) to "claim" all Codex Records. After claiming the
Bulk Transaction, the Codex Records will be automatically transferred to the
claiming user's account.
Here is an example of the Claim Records page (i.e. claimCodeUrl
):
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/v1/client/claim-records/4ACsjkrR',
method: 'post',
json: true,
headers: {
Authorization: 'Bearer d49694e5a3459759cc7ac1741de246e184e51d6e',
},
}
request(options, (error, response) => {
console.log(response.body.result) // the claimed Bulk Transaction object
})
The above API call (eventually) transfers all Codex Records belonging to the Bulk Transaction with
claimCode
"4ACsjkrR" and returns the claimed Bulk Transaction.
You can also claim a Bulk Transaction programmatically by calling the following route. In this case, your application would be claiming another Bulk Transaction (not created by your application.)
HTTP Request
POST v1/client/claim-records/:claimCode
Webhook Events
Event Name | Recipient |
---|---|
bulk-transaction:claim:completed:creator |
Bulk Transaction creator |
bulk-transaction:claim:completed:claim-user |
The user who claimed the Bulk Transaction |
General Concepts
This section explains miscellaneous concepts referenced throughout this documentation.
API Requests
This HTTP request:
PUT /v1/client/record/100/metadata?name=Really%20Cool%20Codex%20Record HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: https://rinkeby-api.codexprotocol.com
Authorization: Bearer d49694e5a3459759cc7ac1741de246e184e51d6e
description=This+is+a+really+cool+Codex+Record!
is handled identically to this one:
PUT /v1/client/record/0/metadata HTTP/1.1
Content-Type: application/json
Host: https://rinkeby-api.codexprotocol.com
Authorization: Bearer e933e3e02e4f16d552e469367b25293d75c3d3c2
{
"name": "Really Cool Codex Record",
"description": "This is a really cool Codex Record!"
}
and this one:
PUT /v1/client/record/0/metadata?description=This%20is%20a%20really%20cool%20Codex%20Record! HTTP/1.1
Host: https://rinkeby-api.codexprotocol.com
Authorization: Bearer e933e3e02e4f16d552e469367b25293d75c3d3c2
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"
Really Cool Codex Record
------WebKitFormBoundary7MA4YWxkTrZu0gW--
The Codex API is very flexible with how it handles incoming requests. For most
routes, the API will accept a request with a Content-Type
of
multipart/form-data
, application/x-www-form-urlencoded
, or
application/json
. Additionally, you may specify request parameters as either
query parameters or body parameters (with the exception of file data, of course.)
The exceptions are:
Anytime you send file data with the request, you must send the request as
Content-Type: multipart/form-data
.When requesting access tokens, you must send the request as
Content-Type: application/x-www-form-urlencoded
. This is required by the OAuth2 Specification.
For details on what to expect from The Codex API in response, see Response Examples.
Webhooks
Calling certain routes may cause asynchronous transactions to be submitted to the blockchain. Specifically, any action that modifies "on-chain" data is asynchronous and has an associated webhook event.
Your application should not assume the result of calling an asynchronous route will be performed immediately. Instead, your application should have an endpoint that can be called by The Codex API when the asynchronous action is complete and the blockchain state has been updated.
For example, when updating modifying a Codex Record's Metadata,
your application should wait until it's webhook is called with the
codex-record:modified
event. At this point your application can react to the
updated data in any way necessary.
Asynchronous Actions
The following actions should be considered asynchronous, and your application should wait for their associated webhook events before considering requests sent to them as successful.
Action | Event(s) |
---|---|
Creating a Codex Record | codex-record:created |
Modifying a Codex Record's Metadata | codex-record:modified |
Starting a Transfer | codex-record:address-approved:owner and codex-record:address-approved:approved |
Canceling a Transfer | codex-record:address-approved:cancel |
Accepting a Transfer | codex-record:transferred:old-owner and codex-record:transferred:new-owner |
Creating Codex Records in Bulk | bulk-transaction:started and bulk-transaction:completed |
Claiming a Bulk Transaction | bulk-transaction:claim:completed:creator and bulk-transaction:claim:completed:claim-user |
Webhook Structure
An example request body sent to your application's webhook:
{
"timestamp": 1539024178127,
"hash": "5fed9779c702a873e8c68872eca959876d9d00a84a1cb5fb58ecd6c3e83f307b",
"network": {
"id": "4",
"name": "rinkeby"
},
"eventName": "codex-record:created",
"eventData": {
// varies by event, usually a Codex Record
}
}
When blockchain events occur, your application's webhook will be sent a POST
request with an application/json
body containing the following data:
Property | Type | Description |
---|---|---|
hash | String | The sha256 hash of the string ${timestamp}:${client_id} , using your application's secret as the key . See Verifying Webhook Requests for details. |
timestamp | Number | Unix timestamp (in milliseconds) of when the request was sent. See Verifying Webhook Requests for details. |
network | Object | The ID and name (both strings) of the Ethereum network the associated transaction was submitted to. This allow your application to use the same webhook endpoint for all environments. |
eventName | String | The name of the event. See Webhook Events for details. |
eventData | Varies | The relevant data for this event. Usually a Codex Record, but varies by event. See Webhook Events for details. |
Webhook Events
The following table lists all webhook events.
Event Name | Event Data | Description |
---|---|---|
codex-record:created | Codex Record | Sent after creating a Codex Record, when the Codex Record has been created and logged on the blockchain. |
codex-record:modified | Codex Record | Sent after modifying a Codex Record's metadata, when the Codex Record has been updated and logged on the blockchain. |
codex-coin:transferred | String (amount of CODX transferred) | Sent when your application receives CODX. Usually this is after requesting CODX from a testnet faucet or purchasing additional CODX on Mainnet. |
codex-record:address-whitelisted | Codex Record | Sent after someone adds your application to a Codex Record's whitelisted addresses. This event is sent immediately as this is not an asynchronous action. |
codex-record:transferred:new-owner | Codex Record | Sent after accepting an incoming transfer, when the Codex Record has been successfully transferred to your address and logged on the blockchain. |
codex-record:transferred:old-owner | Codex Record | Sent after someone accepts one of your application's outgoing transfers, when the Codex Record has been successfully transferred to the recipient's address and logged on the blockchain. |
codex-record:address-approved:owner | Codex Record | Sent after starting a transfer, when the Codex Record's approvedAddress has been updated on the blockchain and is ready to be accepted by the recipient. |
codex-record:address-approved:approved | Codex Record | Sent after someone starts a transfer to your application, when the Codex Record's approvedAddress has been updated on the blockchain and is ready to be accepted by your application. |
codex-record:address-approved:cancel | Codex Record | Sent after cancelling a transfer, when the Codex Record's approvedAddress has been cleared on the blockchain and can no longer be accepted by the recipient. |
bulk-transaction:started | Bulk Transaction | Sent after creating Codex Records in bulk, when transactions start getting sent to the blockchain. |
bulk-transaction:completed | Bulk Transaction | Sent after creating Codex Records in bulk, when all Codex Records have been created and logged on the blockchain. |
bulk-transaction:claim:completed:creator | Bulk Transaction | Sent after someone claims your application's Bulk Transaction, when all Codex Records have been successfully transferred to the recipient's address and logged on the blockchain. |
bulk-transaction:claim:completed:claim-user | Bulk Transaction | Sent after your application claims a Bulk Transaction, when all Codex Records have been successfully transferred to your address and logged on the blockchain. |
Verifying Webhook Requests
A simple webhook implementation that verifies webhook requests from The Codex API.
import crypto from 'crypto'
import express from 'express'
import bodyParser from 'body-parser'
const app = express()
// your webhook will always be passed an application/json request
app.use(bodyParser.json())
app.post('/codex-webhook', (request, response) => {
const { hash, timestamp } = request.body
// if, for some reason, your application's webhook is called without these
// parameters, the request most likely did not come from The Codex API and
// should be immediately rejected
if (!hash || !timestamp) {
response.sendStatus(400)
return
}
// if the specified timestamp is in the future or is more than 5 minutes in
// the past, consider this a fraudulent request
//
// your "timestamp validity" window can be whatever you deem appropriate, but
// we recommend 5 minutes
//
// this is used to prevent the request from being "replayed" at a later date
// (via a man-in-the-middle or similar attack)
const now = Date.now()
if (timestamp > now || now - timestamp > 300000) {
response.sendStatus(400)
return
}
// construct the "valid hash" and compare with the hash sent by The Codex API
const validHash = crypto
.createHmac('sha256', client_secret)
.update(`${timestamp}:${client_id}`)
.digest('hex')
if (hash !== validHash) {
response.sendStatus(401)
return
}
// do whatever your application should do in response to this event here
// let The Codex API know this webhook call was successful
response.sendStatus(200)
})
Remember to replace
client_id
andclient_secret
with your application'sclient_id
andclient_secret
.
The hash
and timestamp
properties passed with every webhook request should
be used to verify that the request was actually sent by The Codex API and hasn't
been spoofed by an attacker. Your application should verify the hash for all
webhook events.
The verify the request, calculate the sha256
hash of the string ${timestamp}:${client_id}
using your application's secret
as the key
. Then compare the result with the hash
specified in the request.
CODX Tokens & Fees
CodexCoin (CODX) is the ERC-20 utility token used to interact with The Codex API. You must have CODX to create and edit Codex Records. See Smart Contract Addresses for Etherscan links for the CodexCoin contracts on each network.
Get CODX From Faucet
On Testnets, you can request CODX for free from the "CODX Faucet". After
requesting a drip from the faucet, CODX will be sent to your account and you
will receive the codex-coin:transferred
webhook event upon success. You can
request 1 drip every 24 hours.
See Request Faucet Drip for details on how to request CODX from the faucet.
Purchase CODX
On Mainnet, you must purchase CODX. Currently, there is no way to do this programmatically so you must login into The Codex Viewer and purchase CODX via Stripe. See Logging Into The Codex Viewer for details.
Privacy
The public nature of blockchain presents some interesting challenges when dealing with information many people people wish to keep private. To take advantage of Ethereum's distributed, public ledger while still protecting our user's private data, Codex Protocol has taken a hybrid "off-chain / on-chain" approach to storing user data.
"Off-Chain" vs "On-Chain" Data
All metadata is stored "off-chain" (i.e. in a metadata provider's database), but hashes of this data are stored on "on-chain" (i.e in a Codex Protocol smart contract.) These hashes can be used to verify the data stored in a metadata provider's database matches the information on the blockchain, and therefore has not been tampered with.
Specifically, the only "on-chain" data are hashes of the Codex Record's name, description, and associated files. Sending a request that will modify "on-chain" data is considered an asynchronous action and will eventually result in your application receiving a webhook event. See webhooks for details.
Updates to any other "off-chain" property of the Codex Record (such as the
isPrivate
flag, or the whitelistedAddresses
array), will be immediately
applied and (usually) won't cause a webhook event to be sent.
This "off-chain / on-chain" data separation is why there are two routes to modify a Codex Record. When you Modify a Codex Record, you're modifying it's "off-chain" data. When you Modify a Codex Record's Metadata, you're modifying it's "on-chain" data.
Permissions
Some properties of a Codex Record and it's metadata are not returned in API
responses (i.e. they will be undefined
) if the Codex Record marked as private
and the requesting user does not have the appropriate permissions.
There are three levels of permissions for Codex Records:
- Owner - full read & write permissions, all data always returned
- Whitelisted Addresses - read-only permissions, most data returned
- Public Access / Non-Whitelisted Addresses - read-only permissions, only public information returned
Additionally, there are two levels of privacy for a Codex Record:
isPrivate
- indicates that the Codex Record's metadata is private and can only be retrieved by the owner or whitelisted addresses.isHistoricalProvenancePrivate
- indicates whether or not the Codex Record's historical provenance should be hidden, regardless of the value ofisPrivate
.
Individual permissions for each property of a Codex Record and it's metadata are listed in Response Examples.
Whitelisted Addresses
Every Codex Record has an "off-chain" list of Ethereum addresses allowed to view
it's private metadata. The whitelistedAddresses
array allows users to provide
read-only access for any address they wish. See Get a Codex Record's Whitelisted Addresses
(and subsequent sections) for details on how to retrieve and update this list.
Even though private metadata is visible to whitelisted addresses, the
isHistoricalProvenancePrivate
flag can be used to hide the
historical provenance for a Codex Record. This way,
the owner can keep some (presumably more confidential) files hidden while still
providing some access to others.
Approved Addresses
A Codex Record can also have an approvedAddress
, which is an Ethereum address
that is allowed to transfer the Codex Record to itself. This is necessary for
the two-step transfer process. See Transferring Codex Records
for details.
Historical Provenance
In addition to an array of images
, a Codex Record may also contain a special
array (files
) that we call the "historical provenance". The intent is for
images
to contain pictures and/or videos of the item itself and for files
to contain other, arbitrary files such as a PDF of physical appraisal documents
or a scan of an original purchase receipt.
Since files in historical provenance may be considered more "confidential", a
separate privacy control is available to keep these files hidden even when being
viewed by someone with permissions to see private metadata. If a
Codex Record's isHistoricalProvenancePrivate
flag is true
, the files
array will always be visible to the owner only.
Transferring Codex Records
Transferring a Codex Record is a two-step process:
The owner approves another user to accept the transfer of the Codex Record. This sets the Codex Record's
approvedAddress
to that user's Ethereum address, and grants them read-only access to the private metadata. At this point, the Codex Record is considered an outgoing transfer for the owner and an incoming transfer for the approved user.The approved user must then explicitly accept the incoming transfer. The approved user may also choose to ignore the transfer, in which case they remain the
approvedAddress
but the Codex Record can be hidden from their list of incoming transfers.
When the Codex Record is successfully transferred to the approvedAddress
and
logged on the blockchain, the approved user becomes the new owner of the record
and a Provenance Event is logged.
Additionally, a few "off-chain" properties are reset to their default values:
Property | Reset To |
---|---|
isIgnored | false |
approvedAddress | null |
whitelistedEmails | [] |
whitelistedAddresses | [] |
isHistoricalProvenancePrivate | true |
Proxy Users
Some applications need to perform actions on behalf of other users. This is currently limited to creating records on behalf of other addresses, and is only supported when creating Codex Records in bulk.
If you believe your application needs proxy user support, please send us an email and we'll be happy to help.
The Codex Viewer
The Codex Viewer is a frontend application written by Codex Protocol that interfaces with The Codex API. This application can be used to create, modify, and transfer Codex Records, as well as purchase CODX and browse public collections.
The Codex Viewer is available in all the same environments as the API:
Environment | Network | URL |
---|---|---|
Development | Ropsten | http://ropsten.codex-viewer.com |
Staging | Rinkeby | https://rinkeby.codex-viewer.com |
Production | Mainnet | https://codex-viewer.com |
Logging Into The Codex Viewer
Even though OAuth2 accounts have direct API access, sometimes it's necessary to log into The Codex Viewer directly (e.g. when purchasing CODX.)
To login, simply visit the Codex Viewer in the appropriate environment (see
table above) and use your application's email
and client_secret
as the
password at the login screen.
Response Examples
An example success response (from a Get Client request):
{
"error": null,
"result": {
"availableCODXBalance": 100,
"email": "email@example.com",
"name": "My Codex Application",
"faucetDripLastRequestedAt": null,
"faucetDripNextRequestAt": "2018-11-26T19:25:03.056Z"
"address": "0x627306090abab3a6e1400e9345bc60c78a8bef57"
}
}
All responses from The Codex API will have the same structure, regardless
of whether or not the request was successful. The Codex API will always
return Content-Type: application/json; charset=utf-8
response bodies with an
object containing the keys error
and result
, one of which will always be
null
. Always returning a JSON body ensures you can always reliably parse
responses from The Codex API as JSON, even if there's an error.
If there was error, result
will be null
and the error details can be found
in the error
object. The error
object has two keys, code
and message
.
error.code
will be the same as the HTTP status header on the response, and
error.message
will provide an explanation of what went wrong.
Similarly, if the request was successful, error
will be null
and the
requested data can be found in the response
object.
An example error response (expired access token):
{
"error": {
"code": 401,
"message": "Invalid token: access token has expired"
},
"result": null
}
Client
{
"availableCODXBalance": 100,
"email": "email@example.com",
"name": "My Codex Application",
"faucetDripLastRequestedAt": null,
"faucetDripNextRequestAt": "2018-11-26T19:25:03.056Z"
"address": "0x627306090abab3a6e1400e9345bc60c78a8bef57"
}
Property | Type | Description |
---|---|---|
name | String | The name of the application. This will be shown in The Codex Viewer as a registered application, taking the place of your application's Ethereum address in the Codex Record’s provenance. |
String | The application developer’s email address. This will be used to communicate any breaking API changes or development updates. | |
address | String | The Ethereum address that identifies the application, provisioned and managed by Codex behalf of the application developer. |
availableCODXBalance | Number | The amount of CODX your application has remaining to spend on transactions. See CODX Tokens & Fees for details. |
faucetDripNextRequestAt | Date | (Testnets only) The time at which your application will next be able to request CODX from the faucet. |
faucetDripLastRequestedAt | Date | (Testnets only) The time at which your application last requested CODX from the faucet. |
OAuth2 Access Token
{
"accessTokenExpiresAt": "2018-10-02T22:36:57.808Z",
"accessToken": "d49694e5a3459759cc7ac1741de246e184e51d6e"
}
Property | Type | Description |
---|---|---|
accessTokenExpiresAt | Date | The date at which this access token will expire. See Access Token Expiration for details. |
accessToken | String | The access token itself. This should be added to the Authorization header of all requests made to The Codex API. |
Codex Record
{
"tokenId": "0",
"isPrivate": false,
"isIgnored": false,
"providerId": "codex",
"isHistoricalProvenancePrivate": true,
"providerMetadataId": "5bae56f75d7971becf854a72",
"ownerAddress": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"approvedAddress": "0x821aea9a577a9b44299b9c15c88cf3087f3b5544",
"nameHash": "0x4955c7fd1cb2de006320e77b157409b84ab2df69d9c0720f609d7a17dd2a674c",
"descriptionHash": "0xcb7ab38fbc9919a3b04eabc343d7e649335ad748213df20e65d1979d0d3aa800",
"whitelistedAddresses": [
"0xf17f52151ebef6c7334fad080c5704d77216b732",
"0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef"
],
"whitelistedEmails": [
"user@example.com"
],
"fileHashes": [
"0xb39e1bbc6d50670aafa88955217ddf1e9e1f635a2cfc8d5d806a730f014c7210"
],
"provenance": [
{
// see " Provenance Entry" below
}
],
"metadata": {
// see "File" below
},
"provider": {
// see "Provider" below
}
}
Property | Type | Permissions | Description |
---|---|---|---|
tokenId | String | Public | The unique identifier of this Codex Record |
metadata | Metadata | Whitelisted Addresses (if isPrivate is true ) |
Will be undefined if isPrivate is true and you are not the owner or a whitelisted address. See Metadata for details. |
provider | Metadata Provider | Public | See Metadata Provider for details. |
nameHash | String | Public | The hash of the metadata's plain text name. |
isIgnored | Boolean | Public | This flag indicates that a user has chosen to "ignore" an incoming Codex Record transfer. This is useful to hide incoming transfers in a User Interface since there's no blockchain mechanism to explicitly reject a transfer. |
isPrivate | Boolean | Public | This flag indicates that the metadata for this record is private and can only be retrieved by the owner, the approvedAddress , and the addresses listed in whitelistedAddresses / whitelistedEmails . |
provenance | Array[Provenance Event] | Public | An array of Provenance Events. |
fileHashes | Array[String] | Public | An array of file hashes that belong to this Codex Record's metadata. |
providerId | String | Public | See unique id of the Metadata Provider. |
ownerAddress | String | Public | The Ethereum address of the client / user that currently owns this Codex Record. |
descriptionHash | String | Public | The hash of the metadata's plain text description. |
approvedAddress | String | Public | The Ethereum address of the user currently approved to accept the transfer of this Codex Record. See Transferring a Codex Record for details. Approved addresses are considered part of the whitelistedAddresses array. See Approved Addresses for details. |
providerMetadataId | String | Public | The unique ID of the metadata that belongs to this Codex Record. |
whitelistedEmails | Array[String] | Owner Only | An array of email addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. Will be undefined if you are not the owner. See Whitelisted Addresses for details. |
whitelistedAddresses | Array[String] | Owner Only | An array of Ethereum addresses allowed to view private metadata for this Codex Record. This allows users to give people read-only access to their Codex Records. Will be undefined if you are not the owner. See Whitelisted Addresses for details. |
isHistoricalProvenancePrivate | Boolean | Public | This flag indicates whether or not historical provenance (i.e. metadata.files ) should be hidden, regardless of the value of isPrivate . |
Metadata
{
"codexRecordTokenId": "0",
"hasPendingUpdates": true,
"id": "5bae56f75d7971becf854a72",
"name": "Really Cool Codex Record",
"description": "This is a really cool Codex Record!",
"creatorAddress": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"nameHash": "0x4955c7fd1cb2de006320e77b157409b84ab2df69d9c0720f609d7a17dd2a674c",
"descriptionHash": "0xcb7ab38fbc9919a3b04eabc343d7e649335ad748213df20e65d1979d0d3aa800",
"fileHashes": [
"0xb39e1bbc6d50670aafa88955217ddf1e9e1f635a2cfc8d5d806a730f014c7210"
],
"additionalMetadata": {
// see "Additional Metadata" below
},
"auctionHouseMetadata": {
// see "Auction House Metadata" below
},
"pendingUpdates": [
{
// see "Pending Update" below
}
],
"mainImage": {
// see "File" below
},
"files": [
{
// see "File" below
}
],
"images": [
{
// see "File" below
}
]
}
Metadata makes up all of the "off-chain" data associated with a Codex Record, e.g. the "plain text" values for the corresponding hashes stored on the blockchain. Storing this (potentially) sensitive data off-chain is necessary for privacy controls, since the nature of blockchain transactions are public.
Property | Type | Permissions | Description |
---|---|---|---|
id | String | Public | The unique ID of the metadata. |
name | String | Whitelisted Addresses (if isPrivate is true ) |
The plain text name of the Codex Record. Will be undefined if isPrivate is true and you are not the owner or a whitelisted address. |
files | Array[File] | Whitelisted Addresses (if isPrivate is true and isHistoricalProvenancePrivate is false ) |
An array of supplemental files that belong to this metadata. This is considered the "historical provenance". Can undefined if isHistoricalProvenancePrivate is true on the Codex Record. See File for details. |
images | Array[File] | Whitelisted Addresses (if isPrivate is true ) |
An array of supplemental images that belong to this metadata. Will be undefined if isPrivate is true and you are not the owner or a whitelisted address. See File for details. |
nameHash | String | Public | The hash of the plain text name. |
mainImage | File | Whitelisted Addresses (if isPrivate is true ) |
The main image of this Codex Record. Will be undefined if isPrivate is true and you are not the owner or a whitelisted address. |
fileHashes | Array[String] | Public | An array of file hashes that belong to this metadata. |
description | String | Whitelisted Addresses (if isPrivate is true ) |
The plain text description of the Codex Record. Will be undefined if isPrivate is true and you are not the owner or a whitelisted address. |
pendingUpdates | Array[Pending Update] | Owner Only | An array of updates that are currently being processed for this metadata. See Pending Update for details. Will be undefined if you are not the owner. |
creatorAddress | String | Public | The Ethereum address of the client / user who created this metadata (not necessarily the current Codex Record owner!) |
descriptionHash | String | Public | The hash of the plain text description. |
hasPendingUpdates | Boolean | Public | This flag is true if there is currently a "modify" transaction processing on the blockchain (e.g. after modifying a Codex Record's metadata). This is useful for showing some sort of loading state on a Codex Record while modifications are pending. See Pending Update for details. |
codexRecordTokenId | String | Public | The tokenId of the Codex Record this metadata belongs to. |
additionalMetadata | Object | Whitelisted Addresses (if isPrivate is true) | A list of additional metadata about the asset. See Additional Metadata for details. |
auctionHouseMetadata | Object | Whitelisted Addresses (if isPrivate is true) | An arbitrary list of data specific to the Auction House that created the Codex Record. See Auction House Metadata for details. |
Additional Metadata
{
"lotNumber": "123A",
"salePrice": "1000",
"saleCurrency": "USD",
"editionNumber": "16",
"totalEditions": "64",
"condition": "Excellent",
"dimensionsInches": "18 x 23",
"creatorName": "Cool Guy McGee"
"auctionName": "My Cool Auction",
"medium": "mixed media on paper",
"signatureLocation": "front, top left"
"dimensionsCentimeters": "45.7 x 58.4",
"assetSoldAt": "2019-03-21T16:30:06.030Z",
"assetCreatedAt": "1993-01-01T00:00:00.000Z",
}
This is a list of additional properties about the asset for which the Codex Record was created. This object is intended to contain information about the physical asset such as it's condition and dimensions. This entire object and all of it's values are optional.
Property | Type | Description |
---|---|---|
make | String | The asset's make. |
model | String | The asset's model. |
medium | String | The asset's medium. |
condition | String | The asset's condition. |
lotNumber | String | The lot number of the auction this asset was part of. |
auctionName | String | The name of the auction this asset was part of. |
artistName | String | The name of the asset's artist. |
creatorName | String | The name of the asset's creator. |
assetSoldAt | Date | When the asset was sold. |
salePrice | String | The amount in local currency the asset sold for. |
saleCurrency | String | The local currency associated with salePrice . |
editionNumber | String | The asset's edition number. |
totalEditions | String | The asset's total number of editions. |
assetCreatedAt | Date | When the asset was created. |
dimensionsInches | String | The asset's dimensions (in inches). |
signatureLocation | String | The location of the creator's signature. |
dimensionsCentimeters | String | The asset's dimensions (in centimeters). |
Auction House Metadata
{
"id": "1234",
"linkbackUrl": "https://example.com/my-auction-house/asset/1234",
// any other arbitrary data (e.g. transactionId, inventoryNumber, artistId, etc...)
}
This is a list of arbitrary data an auction house can specify when creating a Codex Record. This object is intended to contain information specific to the auction house, such as a transaction ID, inventory number, etc.
Pending Update
{
"id": "5bb661ab92d258fdf2efa29b",
"name": "Super Duper New Title",
"description": "This will replace the current description!",
"nameHash": "0x444df25b54a53b842bb2af218e79e8cd2f1efca5c8f7a439451686fb3b825636",
"descriptionHash": "0xcb7ab38fbc9919a3b04eabc343d7e649335ad748213df20e65d1979d0d3aa800",
"fileHashes": [
"0xd7bbfd8fd047f48c6724adb0633059f00f55339ed2fc96a529ef606d6ec76593"
],
"mainImage": {
// see "File" below
},
"files": [
{
// see "File" below
}
],
"images": [
{
// see "File" below
}
]
}
Typically generated after modifying a Codex Record's metadata, a Pending Update represents the new state metadata should have after a "modify" transaction has been mined on the blockchain. This process is necessary due to the asynchronous nature of blockchain transactions.
Property | Type | Description |
---|---|---|
id | String | The unique ID of the pending update. |
name | String | The new plain text name. |
files | Array[File] | The new array of supplemental files. |
images | Array[File] | The new array of supplemental images. |
nameHash | String | The hash of the new plain text name. |
mainImage | File | The new main image. |
fileHashes | Array[String] | An array of file hashes representing all the new files & images. |
description | String | The new plain text description. |
descriptionHash | String | The hash of the new plain text description. |
Provenance Event
{
"type": "transferred",
"oldOwnerAddress": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"newOwnerAddress": "0xf17f52151ebef6c7334fad080c5704d77216b732",
"transactionHash": "0x0a3a518d1a0786cbdc6265dc24fc0112411c996363465a2789dc3d8021cd4c44",
}
A Provenance Event is a record of a blockchain transaction related to a specific
Codex Record. A Codex Record's provenance
array is essentially a transaction
ledger, detailing when it was created, who it was transferred to, and who
modified what data.
Property | Type | Description |
---|---|---|
type | String | One of ['created', 'modified', 'transferred'] . |
oldOwnerAddress | String | The Ethereum address of the owner before this transaction occurred. For created events, this will be the "zero address" (a special address that nobody can own, used as a "null value" for address-type variables.) For modified events, this will be the same as newOwnerAddress . |
newOwnerAddress | String | The Ethereum address of the after before this transaction occurred. For modified events, this will be the same as oldOwnerAddress . |
transactionHash | String | The Ethereum transaction hash of the transaction that emitted this event. |
Metadata Provider
{
"id": "codex",
"description": "The Codex API",
"metadataUrl": "https://rinkeby-api.codexprotocol.com/v1/records/"
}
A Metadata Provider is known source of Codex Record metadata. Since metadata must be stored "off-chain", Metadata Providers are essentially databases / API endpoints which can be used to retrieve Codex Record metadata.
Property | Type | Description |
---|---|---|
id | String | The unique ID of the metadata provider. |
description | String | A description of the metadata provider. |
metadataUrl | String | The URL to request Codex Record Metadata from. The Codex Record's tokenId should be appended to this URL to retrieve metadata about that record. |
File
{
"width": "1024",
"size": "181516",
"height": "1024",
"fileType": "image",
"name": "cool-file.jpg",
"mimeType": "image/jpeg",
"id": "5bae56f65d7971becf854a71",
"creatorAddress": "0x627306090abab3a6e1400e9345bc60c78a8bef57",
"hash": "0xb39e1bbc6d50670aafa88955217ddf1e9e1f635a2cfc8d5d806a730f014c7210",
"uri": "https://s3.amazonaws.com/codex.registry/files/1538152180696.c8a9ec69-0ed8-4516-a34c-e15ebbe4749c.jpg"
}
When files are uploaded to The Codex API, they are processed to determine useful bits of information such as width, height, and size (in bytes). The are then uploaded to S3, and the resulting information is stored in the The Codex API database.
Property | Type | Description |
---|---|---|
id | String | The unique ID of the file. |
uri | String | The URL of the file. |
size | String | The size (in bytes) of the file. |
name | String | The name of the file, as passed in when created. |
hash | String | The hash of the file data. |
width | String | The width (in pixels) of the file, if it is an image. Otherwise null . |
height | String | The height (in pixels) of the file, if it is an image. Otherwise null . |
fileType | String | One of ['file', 'video', or 'document'] . Useful when displaying this file on a web page. |
mimeType | String | The MIME type of the file. |
creatorAddress | String | The Ethereum address of the client / user created this file (not necessarily the current Codex Record owner!) |
Bulk Transaction
{
"id": "5c9162551a54697e9331d88b",
"claimUrl": "https://codex-viewer.com/claim-records/4ACsjkrR",
"claimStatus": "claimed",
"claimCode": "4ACsjkrR",
"type": "record-mint",
"status": "complete",
"recordMintData": {
"numCreated": 3,
"numClaimed": 3,
"insertionErrors": [
"name: Path `name` is required."
],
"existingAuctionHouseUniqueIds": [
"1234"
]
}
}
In this example, 5 metadata objects were specified when creating Codex Records in bulk, one of which failed due to a missing
name
parameter and another failed due to a duplicateauctionHouseMetadata.id
already in The Codex API database for your application.
When bulk routes are are called (e.g. when creating Codex Records in bulk), a Bulk Transaction object is returned. This object can be used to monitor the progress of an in-progress Bulk Transaction, or to retrieve stats about an already-completed Bulk Transaction such as how many Codex Records were created.
Property | Type | Description |
---|---|---|
id | String | The unique ID of the Bulk Transaction. |
type | String | The type of Bulk Transaction, currently the only value this can have is record-mint . |
status | String | One of the following created , pending , or complete . "Created" means the Bulk Transaction is in the queue waiting to be processed, "pending" means it's in progress, and "completed" means the entire Bulk Transaction is done. |
claimUrl | String | If generateClaimCode was specified when creating the Bulk Transaction, this is the url which can be used to claim this Bulk Transaction via The Codex Viewer. |
claimCode | String | If generateClaimCode was specified when creating the Bulk Transaction, this is the code which can be used to claim this Bulk Transaction via The Codex API. |
claimStatus | String | One of the following unclaimed , pending , or claimed . "Unclaimed" means the Bulk Transaction is waiting to be claimed, "pending" means it's currently transferring Codex Records to the claiming user, and "claimed" means the claim process is complete. |
recordMintData[numCreated] | Number | If this Bulk Transaction is of type record-mint , this is the number of Codex Records successfully created. This value is only updated when the status transitions from pending to complete . |
recordMintData[numClaimed] | Number | If this Bulk Transaction is of type record-mint , and generateClaimCode was specified when creating the Bulk Transaction, this is the number of Codex Records successfully transferred to the claiming user. This value is only updated when the claimStatus transitions from pending to claimed . |
recordMintData[insertionErrors] | Array[String] | If this Bulk Transaction is of type record-mint , this is a list of errors that occurred while inserting the soon-to-be-created metadata records. |
recordMintData[existingAuctionHouseUniqueIds] | Array[String] | If this Bulk Transaction is of type record-mint , this is a list of the specified auctionHouseMetadata.id values that correspond to an already-existing Codex Record in The Codex API database for your application. (Duplicates are not created.) |
Errors
The Codex API can return the following error codes:
Error Code | Error Description |
---|---|
401 | Unauthorized Error - Your access token is invalid (e.g. it has expired and you need to request a new one.) |
402 | Payment Required Error - You do not have enough CODX to perform the requested action. On testnets, you may request more CODX from the faucet. On Mainnet, you must purchase additional CODX. |
403 | Forbidden Error - Your access token is valid, but you do not have permission to perform the request action (e.g. "admin only" actions.) |
404 | Not Found Error - The requested resource (or route) does not exist. |
409 | Missing Parameter Error - A required parameter was not specified in the request data. See error message for details. |
409 | Invalid Argument Error - An invalid parameter was specified in the request data (e.g. an invalid email address), or the requested action can not be performed (e.g. cancelling a transfer that hasn't been initiated yet.) See error message for details. |
409 | Conflict Error - The requested resource could not be created because a record with a matching unique identifier already exists. See error message for details. |
500 | Internal Server Error - There was an unknown problem processing the request. Please try again later. |
503 | Service Unavailable - We're temporarily offline for maintenance. Please try again later. |
Blockchain Details
This section contains details about the blockchain specifics of Codex Protocol. It's geared towards developers who are building blockchain-based applications that interface directly with our smart contracts, or who are building NFT wallets and want to correctly display Codex Records.
Smart Contract Addresses
The following table lists the contracts for each environment we deploy to. Click a link to check out that contract on Etherscan.
Ropsten | Rinkeby | Mainnet |
---|---|---|
CodexCoin | CodexCoin | CodexCoin |
CodexRecord | CodexRecord | CodexRecord |
CodexRecordProxy | CodexRecordProxy | CodexRecordProxy |
Listening to Contract Events
Listening for events via the CodexRecordProxy:
import Web3 from 'web3'
const web3 = new Web3(process.env.ETHEREUM_RPC_URL)
const codexRecordProxyAddress = /* see table above */
const codexRecordABI = /* see: https://raw.githubusercontent.com/codex-protocol/npm.ethereum-service/master/static/contracts/1/CodexRecord.json */
const CodexRecord = new web3.eth.Contract(codexRecordABI, codexRecordProxyAddress)
// @NOTE: this is just an example, this would likely pull way too many events so
// you'd need to request event in chunks
CodexRecord
.getPastEvents('allEvents', { fromBlock: 6000000, toBlock: 'latest' })
.then((events) => {
// see https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html?highlight=events#getpastevents
})
To get CodexRecord events, you must specify the ABI of the CodexRecord contract but the address of the CodexRecordProxy contract.
If you wish to subscribe to CodexRecord contract events, please note that events are emitted from the CodexRecordProxy contract address and NOT the CodexRecord contract.
Get ERC-721 Token Metadata
Get a token's metadata for display in an Ethereum wallet with NFT support:
import request from 'request'
const options = {
url: 'https://rinkeby-api.codexprotocol.com/token-metadata/0',
method: 'get',
json: true,
}
request(options, (error, response) => {
console.log(response.body) // ERC-721 Metadata JSON for Codex Record with tokenId 0
})
The above API call returns ERC-721 Metadata JSON, e.g.
{
"name": "Really Cool Codex Record",
"description": "This is a really cool Codex Record!",
"image": "https://s3.amazonaws.com/codex.registry/files/1538152180696.c8a9ec69-0ed8-4516-a34c-e15ebbe4749c.jpg"
}
Our smart contract implements the optional ERC-721 metadata extension, which defines a unique tokenURI for each token in the registry. Since token metadata is dynamic, the tokenURIs point to an endpoint in our API instead of a static file on IPFS.
HTTP Request
GET /token-metadata/:tokenId
URL Parameters
Parameter | Type | Description |
---|---|---|
tokenId | Number | The tokenId of the Codex Record for which to retrieve the metadata of. |