Developing for LUNA
LUNA's API is built on the Apollo GraphQL Framework. The Apollo open source ecosystem provides a robust set of libraries to interface with any GraphQL implementation.
Development Environments
LUNA has three public development spaces:
- Production: available at https://gql.iunu.com/graphql. Stable code on production data released at feature level or hot-fix intervals.
- Edge: available at https://gql.preview.iunu.com/graphql. Nightly builds that include prototype or beta feature. This environment is not considered suitable for production or mission-critical development.
- Sandbox: available at https://gql-demo.iunu.com/graphql. A duplicate of our production code base over statically defined data. Useful for ease of development and testing with a stable code base without using production data. Recommended for the development process.
GraphQL Playground
The standard GraphQL Playground application provides full introspection into the LUNA API.
This screenshot demonstrates the access and introspection capabilities available through GraphQL. The panel on the right shows the business context of the IPM Product API service, as well as additional data about both the data types and the plain language intent behind each property if applicable.
Simple Queries
All of our API objects are first-class citizens with standardized query parameters. Objects can be requested by the following query arguments:
- ID: Every object in our system includes an ID object which is a globally unique integer for every data type. Objects can be requested using either the id: or ids: parameter.
- Code: Most objects in our system have a code field that users can use in lieu of an ID. This allows developers to apply their own unique index that matches the identifiers in our system. In this case, requesting data by the code: or codes: field can be used to retrieve objects.
query {
flag(id: 1) {
id
customerFlagType { // the type of flag being used is a sub-property of the Flag object
flagType { // it can be requested in the reponse for a more verbose description of the Flag
name
}
}
}
}
This query will result in this output
{
"data": {
"flag": [
{
"id": "1",
"customerFlagType": [
{
"flagType": [
{
"name": "Feed"
}
]
}
]
}
]
}
}
Pagination
- Limit and Offset: Pagination can be implemented using these fields.
query {
flag(limit:2,offset:1) {
id
name
}
}
Results in the following output:
{
"data": {
"flag": [
{
"id": "8",
"name": "The Latest Flag"
},
{
"id": "7",
"name": "Deal With It"
}
]
}
}
CRUD Semantics
By default, GraphQL does not support standard REST semantics found in other HTTP API implementations. Our API supports using CRUD as a subset of the object types available through GraphQL, as seen in th eimage above under each "Ops" section. An example update query for a Crop might be as follows:
This only applies to updates to object state (creation, updating, deletion). Regular queries do not require these semantics
mutation {
Crop { // Object type as the parent
create(input: { // CRUD method
code: "user-generated-index" // Users can generate their own unique index properties on the "code" field
name: "Example Crop" // Standard JSON input
productId: 5 // Found from querying the Product service
}) {
id
product { // Request the product object in the resopnse
species {
name // Further request the species name from the product object
}
}
}
}
}
JSON Web Tokens
LUNA's authentication process uses an industry standard JSON Web Token for both long-lived Refresh Tokens and API Bearer tokens. The standard and encoding mechanism are available on at JWT.io
Refresh Tokens
Refresh tokens are long-lived web tokens that provide no direct access to any API service. They are exclusively used to generate access credentials while being easily stored on the server-side for further use. This obfuscates plain-text credentials while ensuring users always have the most up-to-date access to each API service by re-authenticating during each use.
Note: Refresh tokens will live for 7 days. To gain more permanent access to the API, generate API Keys once you have fully authenticated.
To retrieve a Refresh Token, use the following query with your clear-text username and password:
mutation {
RefreshToken {
create(input: {
email: "username@company.plant",
password: "growmore"
}) {
refreshToken
}
}
}
The result should be a nested JSON graph containing a Base64 encoded token
{
"data": {
"RefreshToken": {
"create": {
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaFRva2VuIiwiYWRtaW4iOmZhbHNlfQ.DHnh2kW1VFjRLkTaNcs-NAuq966T8oH8w5J9fpt7_hE"
}
}
}
}
Access Tokens
Retrieving an Access Token is a straight forward step from the Refresh Token. The following query will generate a Bearer token from the above step:
mutation {
AccessToken {
create(
input: {
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaFRva2VuIiwiYWRtaW4iOmZhbHNlfQ.DHnh2kW1VFjRLkTaNcs-NAuq966T8oH8w5J9fpt7_hE"
}
) {
claims
accessToken
}
}
}
The response should contain a list of available APIs given the user access level and the token that can be used to access them.
{
"data": {
"AccessToken": {
"create": {
"userId": 40034,
"claims": [
"customer.get",
"species.get",
"variety.get",
"imperial-mass.get",
"imperial-volume.get",
"product.owner",
"crop.owner"
],
"accessToken": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOlsiY3VzdG9tZXIuZ2V0Iiwic3BlY2llcy5nZXQiLCJ2YXJpZXR5LmdldCIsImltcGVyaWFsLW1hc3MuZ2V0IiwiaW1wZXJpYWwtdm9sdW1lLmdldCIsInByb2R1Y3Qub3duZXIiLCJjcm9wLm93bmVyIl19.3yHR3FjzWVAUXRpFYBAbwErrfVYrXjGtHtWHWo_XvckwusURKTp_cPlsIoZSInSq"
}
}
}
}
Claims and Access Levels
Each service access level is identified by converting its service name - e.g. Customer or IpmProduct - to a kabob-case name, e.g. customer or ipm-product. After the period, users have one of five available access levels:
- Read (get): Users have read-only access to this service
- Update (update): Users can change the object state
- Create (create): Users can create new objects
- Delete (delete): Users can delete objects permanently from the system
- Owner (owner): Users have access to all of the above permissions
The claims array can be read to understand the full access level provided by LUNA. For example, users will only ever have read access to the ImperialMeasure service as this is intended to be standardized across all customers, but have full access to their own Crops.
Accessing Services
To gain access to the API, the access token just needs to be set as an Authorization Header during the HTTP call. The structure of the header is as follows:
Note: The "Bearer: " prefix with a space between the colon and token is required.
{
"Authorization": "Bearer: my.jwt.token"
}
In the GraphQL Playground, the HTTP Headers toggle at the bottom of the screen allows easy access to setting the access token. Once set, users will be able to access any service that the claims array provides.
JSON Web Tokens
LUNA's authentication process uses an industry standard JSON Web Token for both long-lived Refresh Tokens and API Bearer tokens. The standard and encoding mechanism are available on at JWT.io
Refresh Tokens
Refresh tokens are long-lived web tokens that provide no direct access to any API service. They are exclusively used to generate access credentials while being easily stored on the server-side for further use. This obfuscates plain-text credentials while ensuring users always have the most up-to-date access to each API service by re-authenticating during each use.
Note: Refresh tokens will live for 7 days. To gain more permanent access to the API, generate API Keys once you have fully authenticated.
To retrieve a Refresh Token, use the following query with your clear-text username and password:
mutation {
RefreshToken {
create(input: {
email: "username@company.plant",
password: "growmore"
}) {
refreshToken
}
}
}
The result should be a nested JSON graph containing a Base64 encoded token
{
"data": {
"RefreshToken": {
"create": {
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaFRva2VuIiwiYWRtaW4iOmZhbHNlfQ.DHnh2kW1VFjRLkTaNcs-NAuq966T8oH8w5J9fpt7_hE"
}
}
}
}
Access Tokens
Retrieving an Access Token is a straight forward step from the Refresh Token. The following query will generate a Bearer token from the above step:
mutation {
AccessToken {
create(
input: {
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaFRva2VuIiwiYWRtaW4iOmZhbHNlfQ.DHnh2kW1VFjRLkTaNcs-NAuq966T8oH8w5J9fpt7_hE"
}
) {
claims
accessToken
}
}
}
The response should contain a list of available APIs given the user access level and the token that can be used to access them.
{
"data": {
"AccessToken": {
"create": {
"userId": 40034,
"claims": [
"customer.get",
"species.get",
"variety.get",
"imperial-mass.get",
"imperial-volume.get",
"product.owner",
"crop.owner"
],
"accessToken": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOlsiY3VzdG9tZXIuZ2V0Iiwic3BlY2llcy5nZXQiLCJ2YXJpZXR5LmdldCIsImltcGVyaWFsLW1hc3MuZ2V0IiwiaW1wZXJpYWwtdm9sdW1lLmdldCIsInByb2R1Y3Qub3duZXIiLCJjcm9wLm93bmVyIl19.3yHR3FjzWVAUXRpFYBAbwErrfVYrXjGtHtWHWo_XvckwusURKTp_cPlsIoZSInSq"
}
}
}
}
Claims and Access Levels
Each service access level is identified by converting its service name - e.g. Customer or IpmProduct - to a kabob-case name, e.g. customer or ipm-product. After the period, users have one of five available access levels:
- Read (get): Users have read-only access to this service
- Update (update): Users can change the object state
- Create (create): Users can create new objects
- Delete (delete): Users can delete objects permanently from the system
- Owner (owner): Users have access to all of the above permissions
The claims array can be read to understand the full access level provided by LUNA. For example, users will only ever have read access to the ImperialMeasure service as this is intended to be standardized across all customers, but have full access to their own Crops.
Accessing Services
To gain access to the API, the access token just needs to be set as an Authorization Header during the HTTP call. The structure of the header is as follows:
Note: The "Bearer: " prefix with a space between the colon and token is required.
{
"Authorization": "Bearer: my.jwt.token"
}
In the GraphQL Playground, the HTTP Headers toggle at the bottom of the screen allows easy access to setting the access token. Once set, users will be able to access any service that the claims array provides.