API Reference

Welcome to Ulule’s API documentation.

Do you want to dig Ulule’s data or operate crowdfunding campaigns on you own website? This should be a good start.

You must have a Ulule account and provide your API key to retrieve data from API.

If you have any question, just ping us on twitter:

We will keep you posted shortly with new features and business cases. Enjoy!

Overview

HTTP Verbs

Where possible, API strives to use appropriate HTTP verbs for each action.

Currently, we are supporting the following verbs:

Verb Description
OPTIONS Can be issued against any resource to get the HTTP supported methods
HEAD Can be issued against any resource to get just the HTTP header info.
GET Used for retrieving resources.
POST Used for creating resources.

Schema

All API access is over HTTPS, and accessed from the api.ulule.com domain

All data is sent and received as JSON.

$ curl -I https://api.ulule.com/v1/users/3048

HTTP/1.1 200 OK
Server: nginx/1.4.2
Date: Thu, 06 Feb 2014 09:16:48 GMT
Content-Type: application/json
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept, Accept-Encoding, Accept-Language, Cookie
Content-Language: en
Cache-Control: no-cache
X-Nginx-Cache: BYPASS
X-Cache: NO
X-RateLimit-Remaining: 940
X-RateLimit-Reset: 456156
X-RateLimit-Limit: 1000

Blank fields are included as null instead of being omitted.

All timestamps are returned in ISO 8601 format:

YYYY-MM-DDTHH:MM:SSZ

Representations is this documentation

When you fetch a list of resources, the response includes a subset of the attributes for that resource.

In this documentation we will focus on status codes and summary of representations.

Some attributes are very long to describe, the main website is in five languages so we will not print all attributes.

Parameters

Many API methods take optional parameters. For GET requests, any parameters not specified as a segment in the path can be passed as an HTTP query string parameter:

$ curl -i "https://api.ulule.com/v1/users/3048/projects?filter=followed"

In this example, the thoas value are provided for the :user parameter in the path while :filter is passed in the query string.

As we don’t support POST, PATH, PUT and DELETE requests for that moment (our API is currently in read-only), we will provide the documentation for these verbs later.

Errors

HTTP errors

There are five possible types of client errors on API calls that receive request bodies:

  1. Sending invalid JSON will result:
HTTP/1.1 400 Bad Request
  1. Sending the wrong type of JSON values will result:
HTTP/1.1 400 Bad Request
  1. An error occured in the application server will result:
HTTP/1.1 500 Internal Error
  1. Accessing resources without the permissions will result:
HTTP/1.1 401 UNAUTHORIZED
  1. Generating too many API calls will result:
HTTP/1.1 429 Too Many Requests

Validation errors

There are two types of error:

Type (classification) Description
RequiredError A required attribute is missing.
ValueError An attribute value is invalid (wrong format, unsupported, etc).

Errors always go with:

  • fieldNames: array of required/invalid fields
  • message: error description

Required field

When you submit a form and forget a required field, the response will return a 422 status code with the following JSON:

[
    {
        "message": "Required",
        "classification": "RequiredError",
        "fieldNames": [
            "field_name"
        ]
    }
]

Invalid value

When you submit a form with an invalid value, the response will return a 422 status code with the following JSON:

[
    {
        "message": "Error description",
        "classification": "ValueError",
        "fieldNames": [
            "field_name"
        ]
    }
]

Example

HTTP/1.1 422 UNPROCESSABLE ENTITY
Vary: Accept
Content-Type: text/javascript

 [
     {
         "classification": "RequiredError",
         "fieldNames": [
             "amount"
         ],
         "message": "Required"
     },
     {
         "classification": "RequiredError",
         "fieldNames": [
             "country"
         ],
         "message": "Country is required"
     },
     {
         "classification": "ValueError",
         "fieldNames": [
             "nationality"
         ],
         "message": "Nationality is invalid"
     },
     {
         "classification": "ValueError",
         "fieldNames": [
             "lang"
         ],
         "message": "Language is invalid"
     }
 ]

HTTP Redirects

API uses HTTP redirection where appropriate. Clients should assume that any request may result in a redirection. Receiving an HTTP redirection is not an error and clients should follow that redirect. Redirect responses will have a Location header field which contains the URI of the resource to which the client should repeat the requests.

Status code Description
301 Permanent redirection. The URI you used to make the request has been superseded by the one specified in the Location header field. This and all future requests to this resource should be directed to the new URI.
302, 307 Temporary redirection. The request should be repeated verbatim to the URI specified in the Location header field but clients should continue to use the original URI for future requests.

Rate limit

For requests using Basic Authentication or API Key Authentication, you can make up to 500 requests per hour, for DDoS protection you can’t make more than 10 requests per seconds.

You can check the returned HTTP headers of any API request to see your current rate limit status:

X-RateLimit-Remaining: 940
X-RateLimit-Reset: 456156
X-RateLimit-Limit: 1000

The headers tell you everything you need to know about your current rate limit status:

Header name Description
X-RateLimit-Limit The maximum number of requests that the consumer is permitted to make per hour.
X-RateLimit-Remaining The number of requests remaining in the current rate limit window.
X-RateLimit-Reset The time at which the current rate limit window resets in UTC epoch seconds.

Once you go over the rate limit you will receive an error response:

HTTP/1.1 429 Too Many Requests

Languages

Currently api.ulule.com supports the following languages:

Language Code
English en
French fr
Portuguese pt
German de
Italian it
Spanish es

If you want to switch urls provided by our API to another language, you must pass the query string lang in the url.

For example, if I want all urls localized in spanish:

$ curl https://api.ulule.com/v1/projects/9892/comments?lang=es

If you are sending a language not supported by our platform, you will receive following http status code:

HTTP/1.1 501 Not Implemented

Authentication

Most POST operations will require you to have a registered application on Ulule. To do so you must contact the Technical support, we will give you your partner account with a pair of credentials (client_id and client_secret) which will allow you to request an access_token for a dedicated user.

There are three ways to authenticate through the API.

  • HTTP Basic authentication
  • API key authentication
  • OAuth2 token

Requests that require authentication will return 404 Not Found, instead of 401 UNAUTHORIZED, in some places.

HTTP Basic authentication

$ curl --basic --user "username:password" https://api.ulule.com/v1/...

The username:password pair is the same authentication information to log in Ulule.

API key authentication

Instead of the username and password couple you can provide the API key which can be found in your account settings

$ curl -H "Authorization: ApiKey username:YOUR_API_KEY_HERE"  https://api.ulule.com/v1/...

OAuth2 access token

The access_token will allow you to log as a user by passing it in each request.

You can pass it in each request as an authorization header:

$ curl -H "Authorization: Bearer USER-ACCESS-TOKEN"  https://api.ulule.com/v1/...

Or can pass the access_token directly in a GET parameter:

http://www.ulule.com/discover?access_token=[INSERT YOUR ACCESS TOKEN HERE]

Retrieve an access token

To retrieve an access token, we will use this REST endpoint

Request
POST https://www.ulule.com/oauth2/token/

Please, do not forget the slash at the end, just after “token”. This slash is required.

Request must be executed as an HTTP Basic Authentication request with your partner client_id and client_secret as credentials.

POST Parameters

Name Type Description
grant_type String Grant type (value must be password).
username String User username.
password String User password.

Example

$ curl -X POST \
       -d "grant_type=password&username=<username>&password=<password>" \
       -H "Authorization: basic PGNsaWVudF9pZD46PGNsaWVudF9zZWNyZXQ+" https://www.ulule.com/oauth2/token/

PGNsaWVudF9pZD46PGNsaWVudF9zZWNyZXQ+ is base64 of <client_id>:<client_secret>

Response
{
    "access_token": "PKs6dso48MPRPa4pcKjskjIFwpQMA3",
    "token_type": "Bearer",
    "expires_in": 36000,
    "refresh_token": "8Pbypw13OqJDeggvymoUc26djkPZ",
    "scope": "read write"
}

Or use this Python script:

# access_token.py
import base64
import os

import requests

# Your partner client ID.
CLIENT_ID = os.getenv('CLIENT_ID')

# Your partner client secret.
CLIENT_SECRET = os.getenv('CLIENT_SECRET')

# Your Ulule user username.
USERNAME = os.getenv('USERNAME')

# Your Ulule user password.
PASSWORD = os.getenv('PASSWORD')

# We need to encode the client ID and client secret in base64 before
# passing it as a header.
def get_basic_auth_header(user, password):
    user_pass = '{0}:{1}'.format(user, password)
    auth_string = base64.b64encode(user_pass.encode('utf-8'))
    auth_headers = {'AUTHORIZATION': 'Basic ' + auth_string.decode("utf-8")}
    return auth_headers

# Execute the POST request with: grant_type, username and password as
# POST parameters. Do not forget to add HTTP Basic header.
response = requests.post(
    'https://www.ulule.com/oauth2/token/',
    data={
        'grant_type': 'password',
        'username': USERNAME,
        'password': PASSWORD,
    },
    headers=get_basic_auth_header(CLIENT_ID, CLIENT_SECRET))

# Our response.
data = response.json()

# We expect a 200 status code.
assert response.status_code == 200

print('Access-Token: %s' % data.get('access_token'))
print('Refresh-Token: %s' % data.get('refresh_token'))

This script can be easily translatable in your favorite langage.

You can run this script by installing requests package and passing variables as environment variables:

$ pip install requests
$ CLIENT_ID=your-client-id CLIENT_SECRET=your-client-secret USERNAME=user-username PASSWORD=user-password python access_token.py

Access-Token: user-access-token
Refresh-Token: user-refresh-token

You have now an access token to authenticate automatically the user on Ulule.

Refresh your access token

An access token will only be available 10 hours.

As we assume you don’t store your user passwords in plain text in your database you don’t have a way to generate a new access token automatically without asking the user credentials again.

In term of user experience it will be a disaster for your application.

We are introducing the concept of refresh token which will allow you to generate a new access token:

# refresh_token.py
import base64
import os

import requests

# Your partner client ID.
CLIENT_ID = os.getenv('CLIENT_ID')

# Your partner client secret.
CLIENT_SECRET = os.getenv('CLIENT_SECRET')

# Your refresh token.
REFRESH_TOKEN = os.getenv('REFRESH_TOKEN')

# We need to encode the client ID and client secret in base64 before
# passing it as a header.
def get_basic_auth_header(user, password):
    user_pass = '{0}:{1}'.format(user, password)
    auth_string = base64.b64encode(user_pass.encode('utf-8'))
    auth_headers = {'AUTHORIZATION': 'Basic ' + auth_string.decode("utf-8")}
    return auth_headers

# Execute the POST request with: grant_type and refresh_token
# POST parameters. Do not forget to add HTTP Basic header.
response = requests.post(
    'https://www.ulule.com/oauth2/token/',
    data={
        'grant_type': 'refresh_token',
        'refresh_token': REFRESH_TOKEN,
    },
    headers=get_basic_auth_header(CLIENT_ID, CLIENT_SECRET))

# Our response.
data = response.json()

# We expect a 200 status code.
assert response.status_code == 200

print('Access-Token: %s' % data.get('access_token'))

You can run this script by installing requests package and passing variables as environment variables:

$ pip install requests
$ CLIENT_ID=your-client-id CLIENT_SECRET=your-client-secret REFRESH_TOKEN=user-refresh-token python refresh_token.py

Access-Token: user-access-token

You have now a new access token to re-authenticate the user on Ulule.

OAuth2 Ulule connect

Ulule connect allows users to connect their Ulule account to partner websites.

The worklow is:

  1. User is on partner website
  2. Partner needs to access user data
  3. User clicks on “Sign In” button on partner website
  4. This button redirects on Ulule connect URL
  5. If the user does not have an existing Ulule account, the interface will propose to create a new one
  6. If the user already has an account, it can sign in with her Ulule credentials
  7. At the end of sign in / sign up process, user will be redirected to the redirect_uri
  8. The user access token will be returned in the redirect_uri

Ulule connect is based on OAuth2 protocol.

The Ulule connect URL is https://www.ulule.com/oauth2/authorize/.

This URL requires three GET parameters.

Parameter Description
client The partner client ID
response_type The OAuth2 response type (token or code)
redirect_uri The registered partner redirect URI

Ulule Connect URL examples:

https://www.ulule.com/oauth2/authorize?client_id=1a2b3c4d&response_type=code&redirect_uri=http://example.com
https://www.ulule.com/oauth2/authorize?client_id=1a2b3c4d&response_type=token&redirect_uri=http://example.com

A convenient way is to use this URL in a pop-up.

Ulule Connect sign in / sign up form Ulule Connect authorization page

Response Types

Type Description
code OAuth2 authorization code to request a token
token Directly returns access token in redirect URI

If you choose code as response type (OAuth2 authorization grant method), you can obtain a token back with the returned code.

To do so, simply make a POST request on https://www.ulule.com/oauth2/token/ with your partner client ID and client secret in HTTP Basic header and the following POST parameters:

Parameter Value
grant_type authorization_code
code The returned code
redirect_uri The redirected URI used to obtain the code

Take a look with this Python script:

# access_token_from_code.py
import base64
import os

import requests

# Your partner client ID.
CLIENT_ID = os.getenv('CLIENT_ID')

# Your partner client secret.
CLIENT_SECRET = os.getenv('CLIENT_SECRET')

# Your grant code.
GRANT_CODE = os.getenv('GRANT_CODE')

# Your registered redirect URI.
REDIRECT_URI = os.getenv('REDIRECT_URI')

# We need to encode in base64 the client_id and client_secret before
# before passing it as a header.
def get_basic_auth_header(user, password):
    user_pass = '{0}:{1}'.format(user, password)
    auth_string = base64.b64encode(user_pass.encode('utf-8'))
    auth_headers = {'AUTHORIZATION': 'Basic ' + auth_string.decode("utf-8")}
    return auth_headers

# Execute the POST request with: grant_type, code and redirect_uri as
# POST parameters. Do not forget to add HTTP Basic header.
response = requests.post(
    'https://www.ulule.com/oauth2/token/',
    data={
        'grant_type': 'authorization_code',
        'code': GRANT_CODE,
        'redirect_uri': REDIRECT_URI,
    },
    headers=get_basic_auth_header(CLIENT_ID, CLIENT_SECRET))

# Our response.
data = response.json()

# We expect a 200 status code.
assert response.status_code == 200

print('Access-Token: %s' % data.get('access_token'))
print('Refresh-Token: %s' % data.get('refresh_token'))

You can run this script by installing requests package and passing variables as environment variables:

$ pip install requests
$ CLIENT_ID=your-client-id CLIENT_SECRET=your-client-secret GRANT_CODE=your-grant-code REDIRECT_URI=your-redirect-uri python access_token_from_code.py

Access-Token: user-access-token
Refresh-Token: user-refresh-token

If you choose token, you don’t have to do anything else. The access token will be returned in the redirect URI as URL fragment.

Redirect URI

The redirect URI or redirection endpoint, is used to redirect user after sign in or sign up. Only registered redirect URIs are allowed. You can use, obviously, only one registered URI in Ulule connect URL but you can register as many ones as you need.

Core resources

Find below all resources endpoints. The API is based on a RESTful architecture (without HATEOAS). The request body and response body are always formatted in JSON.

User

For partner permissions, you will only retrieve information for users which had been registered via your partner website.

Get detail

GET /users/:id

Retrieve user detail.

Access rights

Permissions: user, partner

Authentication mode:

Request

GET /users/3048 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
  "id": 1257099,
  "date_joined": "2016-10-08T15:37:48Z",
  "first_name": "Julien",
  "has_avatar": true,
  "lang": "fr",
  "last_login": "2016-12-22T14:05:09.258787Z",
  "last_name": "FOO",
  "birthday": "1955-09-17",
  "country": "FR",
  "nationality": "FR",
  "name": "Julien FOO",
  "is_staff": false,
  "username": "julien-foo",
  "timezone": "Europe/Paris",
  "is_completed": true,
  "absolute_url": "https://www.ulule.com/julien-foo",
  "resource_uri": "https://api.ulule.com/v1/users/123",
  "urls": {
    "web": {
        "absolute": "https://www.ulule.com/julien-foo",
        "account": {
        "wallet": "https://www.ulule.com/users/1232/settings/wallet"
        },
        "discussion": "https://www.ulule.com/discussions/compose/123",
        "public": "https://www.ulule.com/julien-foo"
    }
  },
  "avatar": {
    "20": "https://img.ulule.com/.../20x20.../julien-foo.jpg",
    "30": "https://img.ulule.com/.../30x30.../julien-foo.jpg",
    "40": "https://img.ulule.com/.../40x40.../julien-foo.jpg",
    "55": "https://img.ulule.com/.../55x55.../julien-foo.jpg",
    "75": "https://img.ulule.com/.../75x75.../julien-foo.jpg",
    "90": "https://img.ulule.com/.../90x90.../julien-foo.jpg",
    "128": "https://img.ulule.com/.../128x128.../julien-foo.jpg",
    "180": "https://img.ulule.com/.../180x180.../julien-foo.jpg",
    "230": "https://img.ulule.com/.../230x230.../julien-foo.jpg",
    "290": "https://img.ulule.com/.../290x290.../julien-foo.jpg",
    "full": "https://drfhlmcehrc34.cloudfront.net.../julien-foo.jpg",
    "value": "avatars/2017/01/03/julien-foo.jpg"
  },
  "description": "my description",
  "presentation": "my presentation",
  "screenname": "julien-foo",
  "email": "foo@email.com",
  "personal_id_number": null
  }

Update user

PATCH /users/:id

Update attributes for a given user.

Access rights

Permissions: user, partner

Authentication mode:

Request

PATCH /users/123 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

PATCH parameters

nationality string User nationality (ISO 3166-1 code). country string User country of residence (ISO 3166-1 code). birthday string User birth date (formatted as YYYY-MM-DD and year must be higher to 1901). first_name string User first name (30 characters max.). last_name string User last name (30 characters max.). email string User email. lang string User language (de, en, es, fr, it, nl). ip_address string User IP address

(for ip_address use IPv4 format or IPv6 format)

Example

{
  "nationality": "FR",
  "country": "FR",
  "birthday": "1982-11-01",
  "first_name": "Gilles",
  "last_name": "Fabio",
  "email": "gilles@ulule.com",
  "lang": "fr"
}

Response

If a user has provided all required information, is_completed attribute will be set to true.

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

  {
   "id": 52547,
   "avatar": {
      "20": "https://img.ulule.com/display/d1b7.../user.092550.092557.jpeg",
      "30": "https://img.ulule.com/display/dc27.../user.092550.092557.jpeg",
      "40": "https://img.ulule.com/display/feac.../user.092550.092557.jpeg",
      "55": "https://img.ulule.com/display/fc6a.../user.092550.092557.jpeg",
      "75": "https://img.ulule.com/display/6821.../user.092550.092557.jpeg",
      "90": "https://img.ulule.com/display/8968.../user.092550.092557.jpeg",
      "128": "https://img.ulule.com/display/243.../user.092550.092557.jpeg",
      "180": "https://img.ulule.com/display/538.../user.092550.092557.jpeg",
      "230": "https://img.ulule.com/display/e7c.../user.092550.092557.jpeg",
      "290": "https://img.ulule.com/display/674.../user.092550.092557.jpeg",
      "full": "https://drfhlmcehrc34.cloudfront.net/avatar/.../user.092550.092557.jpeg",
      "value": "avatar/.../user.092550.092557.jpeg"
   },
   "date_joined": "2012-06-07T11:10:50Z",
   "first_name": "Gilles",
   "has_avatar": true,
   "lang": "fr",
   "last_login": "2016-07-06T15:58:21.144092Z",
   "last_name": "Fabio",
   "birthday": "1982-11-01",
   "name": "gillesfabio",
   "is_staff": true,
   "country": "FR",
   "nationality": "FR",
   "urls": {
      "web": {
            "absolute": "https://www.ulule.com/gillesfabio",
            "account": {
               "wallet": "https://www.ulule.com/users/52547/settings/wallet"
            },
            "discussion": "https://www.ulule.com/discussions/compose/52547",
            "public": "https://www.ulule.com/gillesfabio"
      }
   },
   "username": "gillesfabio",
   "timezone": "Europe/Paris",
   "absolute_url": "https://www.ulule.com/gillesfabio",
   "resource_uri": "https://api.ulule.com/v1/users/52547",
   "email": "gilles@ulule.com",
   "is_completed": true
}

List user addresses

GET /users/:id/addresses

Retrieve addresses for a given user.

Access rights

Permissions: user, partner

Authentication mode:

Request

GET /users/123/addresses HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
   "addresses": [
         {
            "id": 555200,
            "first_name": "Florent",
            "last_name": "Foo",
            "entity_name": "",
            "address1": "8 Rue Saint-Fiacre",
            "address2": "",
            "postal_code": "75002",
            "city": "Paris",
            "state": "",
            "country": "FR",
            "user_id": 3048
         }
   ]
}

Create user address

POST /users/:id/addresses

Create an address for a given user.

Access rights

Permissions: user, partner

Authentication mode:

Request

POST /users/123/addresses HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

POST parameters

first_name string First name last_name string Last name address1 string Street address2 string Street — complementary (optional) postal_code string Postal code city string City state string State type string The type of address: personal|business|association entity_name string The name of the address (home, work, etc...) country string Country (ISO 3166-1 code)

Example

{
   "entity_name": "work",
   "first_name": "Florent",
   "last_name": "Foo",
   "address1": "8 Rue Saint-Fiacre",
   "postal_code": "75002",
   "city": "Paris",
   "country": "FR",
   "type": "business"
}

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

{
   "id": 555200,
   "first_name": "Florent",
   "last_name": "Foo",
   "entity_name": "work",
   "address1": "8 Rue Saint-Fiacre",
   "address2": "",
   "postal_code": "75002",
   "city": "Paris",
   "state": "",
   "country": "FR",
   "user_id": 3048,
   "type": "business"
}

List proposals

GET /users/:id/proposals

User proposals (all, new, valid and invalid) organized in a pagination.

Access rights

Permissions: user, partner

Authentication mode:

Request

GET /users/3048/proposals HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

Query parameters

status Optional, the current filter for proposals: new | valid | invalid.

GET /users/3048/proposals?status=new HTTP/1.1

List projects

GET /users/:id/projects

Retrieve user projects (all, created, followed and supported).

You will see a projects section in the response.

Access rights

Permissions: user, partner

Authentication mode:

Request

GET /users/3048/projects HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

Query parameters

state Optional, the current filter for projects: created | followed | supported

GET /users/3048/projects?state=created HTTP/1.1

List orders

GET /users/:id/orders

Retrieve valid orders from a specific user (organized in a pagination).

Access rights

Permissions: user, partner

Authentication mode:

Request

GET /users/3048/orders HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the user ID

Query parameters

status Optional, a filter by order status: selecting-payment | awaiting-confirmation | payment-completed, shipped | cancelled | aborted | payment-done | payment-invalid | payment-reimbursed | payment-refunded | error

GET /users/3048/orders?filter=selecting-payment HTTP/1.1

Address

Get detail

GET /addresses/:id

Retrieve a given address.

Access rights

Permissions: address owner, partner

Authentication mode:

Request

GET /addresses/123 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the address ID

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
   "id": 1,
   "first_name": "Florent",
   "last_name": "Foo",
   "entity_name": "work",
   "address1": "8 Rue Saint-Fiacre",
   "address2": "",
   "postal_code": "75002",
   "city": "Paris",
   "state": "",
   "country": "FR",
   "user_id": 3048,
   "type": "business"
}

Update address

PATCH /addresses/:id

Update a given address.

Access rights

Permissions: address owner, partner

Authentication mode:

Request

PATCH /addresses/123 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

PATCH parameters

first_name string First name last_name string Last name address1 string Street address2 string Street — complementary postal_code string Postal code city string City state string State type string The type of address: personal|business|association entity_name string The name of the address (home, work, etc...) country string Country (ISO 3166-1 code)

You cannot update address country if this address belongs to an order with shippings.

Example

{
   "entity_name": "work",
   "first_name": "Florent",
   "last_name": "Foo",
   "address1": "8 Rue Saint-Fiacre",
   "postal_code": "75002",
   "city": "Paris",
   "country": "FR",
   "type": "business"
}

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

{
   "id": 555200,
   "first_name": "Florent",
   "last_name": "Foo",
   "entity_name": "work",
   "address1": "8 Rue Saint-Fiacre",
   "address2": "",
   "postal_code": "75002",
   "city": "Paris",
   "state": "",
   "country": "FR",
   "user_id": 3048,
   "type": "business"
}

Project

Get detail

GET /projects/:id

Retrieve project detail. There are two types of project on Ulule, the Presale or the Project. They are represented by a code in JSON field type

the following codes:

Type Code
Presale 1
Project 2

Access rights

Permissions: none

Authentication mode:

Request

GET /projects/9892 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

List orders

For partner permissions, you will only retrieve information for projects which are online via your partner website.

GET /projects/:id/orders

Retrieve valid orders from a specific project.

Access rights

Permissions: project owner, partner

Authentication mode:

Request

GET /projects/9892/orders HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

List comments

GET /projects/:id/comments

Retrieve project comments (organized in a pagination).

Access rights

Permissions: authenticated user

Authentication mode:

Request

GET /projects/9892/comments HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

List news

GET /projects/:id/news

Retrieve project news (organized in a pagination).

Access rights

Permissions: authenticated user

Authentication mode:

Request

GET /projects/9892/news HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

List supporters

For partner permissions, you will only retrieve information for projects which are online via your partner website.

GET /projects/:id/supporters

Retrieve project supporters (organized in a pagination).

Access rights

Permissions: project owner, partner

Authentication mode:

Request

GET /projects/9892/supporters HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

List partnerships

For partner permissions, you will only retrieve information for projects which are online via your partner website.

GET /projects/:id/partnerships

Retrieve project partnerships (without pagination).

Access rights

Permissions: project owner, partner

Authentication mode:

Request

GET /projects/9892/partnerships HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

List sponsorships

For partner permissions, you will only retrieve information for projects which are online via your partner website.

GET /projects/:id/sponsorships

Retrieve project sponsorships (without pagination).

coefficient refers to the multiply factor, for 1€ the sponsor adds 1*coefficient €. E.g. if coefficient equals 2, when a backer gives 1€, the sponsor gives 2€. amount value indicates the maximum amount a sponsor will add to a project. The sponsor can no longer contribute once this amount is reached.

Access rights

Permissions: project owner, partner

Authentication mode:

Request

GET /projects/9892/sponsorships HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
   "sponsorships": [
      {
         "id": 1,
         "project_id": 2,
         "coefficient": 1,
         "amount": 5,
         "sponsor": {
            "id": 1,
            "name": "sponsorFoo",
            "type": "premium",
            "link": "https://fr.ulule.com/discover/",
            "position": 1,
            "image": {
               "230x126": "https://...sponsors/...aa8e8709b7d8a5.jpg",
               "258x145": "https://...sponsors/...aa8e8709b7d8a5.jpg",
               "640x360": "https://...sponsors/...aa8e8709b7d8a5.jpg",
               "full": "https://.../sponsors/...aa8e8709b7d8a5.jpg",
               "value": "sponsors/...aa8e8709b7d8a5.jpg"
            },
            "user": {
               "id": 2,
               "date_joined": "0001-01-01T00:00:00Z",
               "first_name": "julien",
               "has_avatar": false,
               "lang": "fr",
               "last_login": "0001-01-01T00:00:00Z",
               "last_name": "groch",
               "birthday": "1987-02-19",
               "country": "FR",
               "nationality": "FR",
               "name": "julien foo",
               "is_staff": true,
               "username": "julien",
               "timezone": "Europe/Paris",
               "is_completed": true,
               "absolute_url": "https://www.ulule.com/julien",
               "resource_uri": "https://api.ulule.com/v1/users/2",
               "urls": {
                  "web": {
                  "absolute": "https://www.ulule.com/julien",
                  "account": {
                     "wallet": "https://.../users/2/settings/wallet"
                  },
                  "discussion": "https://.../discussions/compose/2",
                  "public": "https://www.ulule.com/julien"
                  }
               },
               "avatar": {
                  "20": "https://.../site/img/avatars/avatar.20.png",
                  "30": "https://.../site/img/avatars/avatar.30.png",
                  "40": "https://.../site/img/avatars/avatar.40.png",
                  "55": "https://.../site/img/avatars/avatar.55.png",
                  "75": "https://.../site/img/avatars/avatar.75.png",
                  "90": "https://.../site/img/avatars/avatar.90.png",
                  "128": "https://.../site/img/avatars/avatar.128.png",
                  "180": "https://.../site/img/avatars/avatar.180.png",
                  "230": "https://.../site/img/avatars/avatar.230.png",
                  "290": "https://.../site/img/avatars/avatar.290.png",
                  "full": "https://.../site/img/avatars/avatar.png",
                  "value": "site/img/avatars/avatar.png"
               },
              "description": "Voici ma description",
              "presentation": "<p>voici ma presentation (description compl&egrave;te)</p>",
              "screenname": "julien",
              "email": "foo@ulule.com",
              "personal_id_number": null
            }
         },
         "title": "a spsonsor",
         "description": "a spsonsor description"
      }
   ]
}

Order

Get detail

GET /orders/:id

Retrieve order detail.

Access rights

Permissions: order owner, project owner, partner

Authentication mode:

Request

GET /orders/1 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the order identifier

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
   "id": 246636,
   "order_subtotal": "50.00",
   "order_total": "50.00",
   "order_shipping_total": 0,
   "payment_method": "creditcard",
   "status": 3,
   "status_display": "Awaiting confirmation",
   "absolute_url": "https://www.ulule.com/9892/edit/order/246636/detail/",
   "resource_uri": "https://api.ulule.com/v1/orders/246636",
   "billing_address": null,
   "shipping_address": null,
   "items": [
         {
            "line_total": 50,
            "line_subtotal": 50,
            "line_shipping_total": 0,
            "quantity": 1,
            "unit_price": 50,
            "reward_id": 1234,
            "reward": {
               "id": 897,
               "description_de": "",
               "description_en": "<p>an item</p>\n",
               "description_es": "",
               "description_fr": "",
               "description_it": "",
               "description_nl": "",
               "description_pt": "",
               "price": 50,
               "stock": null,
               "stock_available": null,
               "stock_taken": 236,
               "available": true,
               "shipping_nat": null,
               "shipping_int": null,
               "has_shippings": false,
               "shippings": null
            }
         }
   ],
   "created_at": "2013-07-12T21:58:44.603949Z",
   "user": {
         "id": 241422,
         "avatar": {
            "20": "https://.../site/img/avatars/avatar.20.png",
            "30": "https://.../site/img/avatars/avatar.30.png",
            "40": "https://.../site/img/avatars/avatar.40.png",
            "55": "https://.../site/img/avatars/avatar.55.png",
            "75": "https://.../site/img/avatars/avatar.75.png",
            "90": "https://.../site/img/avatars/avatar.90.png",
            "128": "https://.../site/img/avatars/avatar.128.png",
            "180": "https://.../site/img/avatars/avatar.180.png",
            "230": "https://.../site/img/avatars/avatar.230.png",
            "290": "https://.../site/img/avatars/avatar.290.png",
            "full": "https://....cloudfront.net/avatars/avatar.png",
            "value": "avatars/avatar.png"
         },
         "date_joined": "2013-07-12T21:58:28.730544Z",
         "first_name": "Aurélien",
         "has_avatar": false,
         "lang": "fr",
         "last_login": "2013-07-13T08:25:08.793417Z",
         "last_name": "Richard",
         "birthday": "1994-07-31",
         "name": "Aurélien Richard",
         "is_staff": false,
         "urls": {
            "web": {
               "absolute": "https://www.ulule.com/legarewyn",
               "account": {
                     "wallet": "https://.../users/241422/settings/wallet"
               },
               "discussion": "https://.../discussions/compose/241422",
               "public": "https://www.ulule.com/legarewyn"
            }
         },
         "username": "legarewyn",
         "timezone": "Europe/Paris",
         "absolute_url": "https://www.ulule.com/legarewyn",
         "resource_uri": "https://api.ulule.com/v1/users/241422",
         "email": "r.aurel45@gmail.com"
   },
   "project_id": 9892,
   "payment_url": "https://www.ulule.com/projects/9892/checkout/pay/236636"
}

When you are retrieving an order you will except these statuses:

Label Code
Processing 1
Selecting payment 2
Awaiting confirmation 3
Payment completed 4
Shipped 5
Cancelled 6
Payment done 7
Aborted 8
Payment invalid 9
Payment reimbursed to e-wallet 10
Payment reimbursed 11
Error 12

Create order

With this endpoint you will be able to create an order with a specific user, the response will return you the payment_url and you will have to redirect your user to this url.

Project types

Order creation payload depends of the project type:

  • Presale: orders are quantity-based (type 1)
  • Project: orders are amount-based (type 2)

The difference is very important and affects your request.

For a quantity-based project (type 1), or “presale”, an order must provide an array of rewards (in contrast to amount-based project that can only contain a single one).

The provided array must contain, at least, one reward. For each reward, we need to provide the given reward ID and the desired quantity. For example, we have a “sticker” reward with ID 54 and we want two stickers. So: {"reward_id": 54, "quantity": 2}.

For an amount-based project (type 2), an order can provide either only one existing project’s reward with the related amount (or more, it’s up to your user) or we can skip the reward and only give the amount we wish.

For example, we can either choose the “Super” €100 reward (and even decide to give even more money) or we can just give €200 without selecting any reward.

One important point, when you won’t select any rewards, there is a minimum amount for project (type 2) that you can find in project detail attribute lowest_contribution_amount.

Rewards

Project rewards can be retrieved via the endpoint: project detail

This endpoint returns project detail information with related rewards and their respective IDs in the rewards array attribute.

Response example:

{
    // Other project detail attributes...
    "rewards": [
        {
            "id": 189301,
            "name_de": "",
            "name_en": "",
            "name_es": "",
            "name_fr": "",
            "name_it": "",
            "name_nl": "",
            "name_pt": "",
            "description_de": "",
            "description_en": "",
            "description_es": "",
            "description_fr": "<p>téléchargement de l'ebook guide de survie aux réunions de 145 pages reprenant 40 techniques d'animation de réunions</p> ",
            "description_it": "",
            "description_nl": "",
            "description_pt": "",
            "price": 25,
            "stock": null,
            "stock_available": null,
            "stock_taken": 0,
            "available": true
            "shipping_nat": null,
            "shipping_int": null,
            "shippings": null,
            "has_shippings": false
        },
        {
            "id": 189302,
            "name_de": "",
            "name_en": "",
            "name_es": "",
            "name_fr": "",
            "name_it": "",
            "name_nl": "",
            "name_pt": "",
            "description_de": "",
            "description_en": "",
            "description_es": "",
            "description_fr": "<p>kit de survie aux réunions</p> ",
            "description_it": "",
            "description_nl": "",
            "description_pt": "",
            "price": 100,
            "stock": null,
            "stock_available": null,
            "stock_taken": 0,
            "available": true,
            "shipping_nat": 10.0,
            "shipping_int": 20.0
            "shippings": null,
            "has_shippings": true
        }
    ]
}

Presale

POST /projects/:id/orders

Create an order for a given presale.

Requirements

  • User must have is_completed attribute set to true

Access rights

Permissions: project owner, partner

Authentication mode:

Request

POST /projects/123/orders HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

POST parameters

Order attributes

payment_method* string Payment method (`creditcard`, maestro or directdebit). return_url* string Return URL (user will be redirected to this URL after payment). country string order country rewards* object Selected rewards.

Reward attributes

reward_id* int Reward ID (must be a project’s reward). quantity* int Reward quantity (must be less or equal to reward’s stock_available attribute value).

Example

{
  "return_url": "https://example.com",
  "payment_method": "creditcard",
  "rewards": [
    {"reward_id": 189301, "quantity": 1},
  ]
}

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

{
   "id": 246636,
   "order_subtotal": "50.00",
   "order_total": "50.00",
   "order_shipping_total": 0,
   "payment_method": "creditcard",
   "status": 3,
   "status_display": "Awaiting confirmation",
   "absolute_url": "https://www.ulule.com/9892/edit/order/246636/detail/",
   "resource_uri": "https://api.ulule.com/v1/orders/246636",
   "billing_address": null,
   "shipping_address": null,
   "items": [
         {
            "line_total": 50,
            "line_subtotal": 50,
            "line_shipping_total": 0,
            "quantity": 1,
            "unit_price": 50,
            "reward_id": 1234,
            "reward": {
               "id": 897,
               "description_de": "",
               "description_en": "<p>an item</p>\n",
               "description_es": "",
               "description_fr": "",
               "description_it": "",
               "description_nl": "",
               "description_pt": "",
               "price": 50,
               "stock": null,
               "stock_available": null,
               "stock_taken": 236,
               "available": true,
               "shipping_nat": null,
               "shipping_int": null,
               "has_shippings": false,
               "shippings": null
            }
         }
   ],
   "created_at": "2013-07-12T21:58:44.603949Z",
   "user": {
         "id": 241422,
         "avatar": {
            "20": "https://.../site/img/avatars/avatar.20.png",
            "30": "https://.../site/img/avatars/avatar.30.png",
            "40": "https://.../site/img/avatars/avatar.40.png",
            "55": "https://.../site/img/avatars/avatar.55.png",
            "75": "https://.../site/img/avatars/avatar.75.png",
            "90": "https://.../site/img/avatars/avatar.90.png",
            "128": "https://.../site/img/avatars/avatar.128.png",
            "180": "https://.../site/img/avatars/avatar.180.png",
            "230": "https://.../site/img/avatars/avatar.230.png",
            "290": "https://.../site/img/avatars/avatar.290.png",
            "full": "https://....cloudfront.net/avatars/avatar.png",
            "value": "avatars/avatar.png"
         },
         "date_joined": "2013-07-12T21:58:28.730544Z",
         "first_name": "Aurélien",
         "has_avatar": false,
         "lang": "fr",
         "last_login": "2013-07-13T08:25:08.793417Z",
         "last_name": "Richard",
         "birthday": "1994-07-31",
         "name": "Aurélien Richard",
         "is_staff": false,
         "urls": {
            "web": {
               "absolute": "https://www.ulule.com/legarewyn",
               "account": {
                     "wallet": "https://.../users/241422/settings/wallet"
               },
               "discussion": "https://.../discussions/compose/241422",
               "public": "https://www.ulule.com/legarewyn"
            }
         },
         "username": "legarewyn",
         "timezone": "Europe/Paris",
         "absolute_url": "https://www.ulule.com/legarewyn",
         "resource_uri": "https://api.ulule.com/v1/users/241422",
         "email": "r.aurel45@gmail.com"
   },
   "project_id": 9892,
   "payment_url": "https://www.ulule.com/projects/9892/checkout/pay/236636"
}

Project

POST /projects/:id/orders

Create an order for a given project.

Requirements

  • User must have is_completed attribute set to true

Access rights

Permissions: project owner, partner

Authentication mode:

Request

POST /projects/123/orders HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

POST parameters

reward_id int Reward ID (must be a project’s reward and can be optional). amount int Contribution amount (if ``reward_id`` is provided, amount must be higher or equal to the selected reward). payment_method* string Payment method (creditcard, maestro or directdebit). return_url* string Return URL (user will be redirected to this URL after payment). country string order country

Why reward ID can be optional?

A user can contribute without choosing any reward. He is also free to give any amount but this amount must be higher or equal to the project attribute lowest_contribution_amount value.

If you send a reward_id, the amount value must be higher or equal to the selected reward.

For example, if project’s rewards are:

{
    // other project detail attributes
    "lowest_contribution_amount": 5,
    "rewards": [
        {
            "id": 1235,
            "name_de": "",
            "name_en": "Reward #2 (50€)",
            "name_es": "",
            "name_fr": "",
            "name_it": "",
            "name_nl": "",
            "name_pt": "",
            "description_de": "",
            "description_en": "The second reward.",
            "description_es": "",
            "description_fr": "",
            "description_it": "",
            "description_nl": "",
            "description_p"t: "",
            "price": 50,
            "stock": null,
            "stock_available": null,
            "stock_taken":null,
            "available": true,
            "shipping_nat": null,
            "shipping_int": null,
            "shippings": null,
            "has_shippings": false
        },
        {
            "id": 1234,
            "name_de": "",
            "name_en": "Reward #1 (10€)",
            "name_es": "",
            "name_fr": "",
            "name_it": "",
            "name_nl": "",
            "name_pt": "",
            "description_de": "",
            "description_en": "The first reward.",
            "description_es": "",
            "description_fr": "",
            "description_it": "",
            "description_nl": "",
            "description_p"t: "",
            "price": 10,
            "stock": null,
            "stock_available": null,
            "stock_taken":null,
            "available": true,
            "shipping_nat": null,
            "shipping_int": null,
            "shippings": null,
            "has_shippings": false
        }
    ]
}

The project example has the lowest_contribution_amount attribute equal to 5 euro. The amount must be higher or equal to 5 euro if any reward isn’t selected. If the selected item is the “Reward #2”, 50 euro or more must be given.

Example

I can give 12 euro and select a reward of 10 euro.

{
   "reward_id": 1234,
   "amount": 12,
   "return_url": "https://example.com",
   "payment_method": "creditcard"
}

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

{
   "id": 246636,
   "order_subtotal": "12.00",
   "order_total": "12.00",
   "order_shipping_total": 0,
   "payment_method": "creditcard",
   "status": 3,
   "status_display": "Awaiting confirmation",
   "absolute_url": "https://www.ulule.com/9892/edit/order/246636/detail/",
   "resource_uri": "https://api.ulule.com/v1/orders/246636",
   "billing_address": null,
   "shipping_address": null,
   "items": [
         {
            "line_total": 50,
            "line_subtotal": 50,
            "line_shipping_total": 0,
            "quantity": 1,
            "unit_price": 50,
            "reward_id": 1234,
            "reward": {
               "id": 897,
               "description_de": "",
               "description_en": "<p>an item</p>\n",
               "description_es": "",
               "description_fr": "",
               "description_it": "",
               "description_nl": "",
               "description_pt": "",
               "price": 50,
               "stock": null,
               "stock_available": null,
               "stock_taken": 236,
               "available": true,
               "shipping_nat": null,
               "shipping_int": null,
               "has_shippings": false,
               "shippings": null
            }
         }
   ],
   "created_at": "2013-07-12T21:58:44.603949Z",
   "user": {
         "id": 241422,
          "avatar": {
            "20": "https://.../site/img/avatars/avatar.20.png",
            "30": "https://.../site/img/avatars/avatar.30.png",
            "40": "https://.../site/img/avatars/avatar.40.png",
            "55": "https://.../site/img/avatars/avatar.55.png",
            "75": "https://.../site/img/avatars/avatar.75.png",
            "90": "https://.../site/img/avatars/avatar.90.png",
            "128": "https://.../site/img/avatars/avatar.128.png",
            "180": "https://.../site/img/avatars/avatar.180.png",
            "230": "https://.../site/img/avatars/avatar.230.png",
            "290": "https://.../site/img/avatars/avatar.290.png",
            "full": "https://....cloudfront.net/avatars/avatar.png",
            "value": "avatars/avatar.png"
         },
         "date_joined": "2013-07-12T21:58:28.730544Z",
         "first_name": "Aurélien",
         "has_avatar": false,
         "lang": "fr",
         "last_login": "2013-07-13T08:25:08.793417Z",
         "last_name": "Richard",
         "birthday": "1994-07-31",
         "name": "Aurélien Richard",
         "is_staff": false,
         "urls": {
            "web": {
               "absolute": "https://www.ulule.com/legarewyn",
               "account": {
                     "wallet": "https://.../users/241422/settings/wallet"
               },
               "discussion": "https://.../discussions/compose/241422",
               "public": "https://www.ulule.com/legarewyn"
            }
         },
         "username": "legarewyn",
         "timezone": "Europe/Paris",
         "absolute_url": "https://www.ulule.com/legarewyn",
         "resource_uri": "https://api.ulule.com/v1/users/241422",
         "email": "r.aurel45@gmail.com"
   },
   "project_id": 9892,
   "payment_url": "https://www.ulule.com/projects/9892/checkout/pay/236636"
}

Update order

PATCH /orders/:id

Update address of a given order.

Access rights

Permissions: order owner, project owner, partner

Authentication mode:

Request

PATCH /orders/123 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript
  • Provided addresses should belong to the order user
  • You will not be able to update the shipping address if an order already contains a shipping address and a reward has shippings

Path parameters

id required, the order ID

PATCH parameters

shipping_address_id string The shipping address ID (optional) billing_address_id string The billing address ID (optional)

Example

{
   "shipping_address_id": 555200
}

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
   "id": 267272,
   "order_subtotal": "20.00",
   "order_total": "20.00",
   "order_shipping_total": 0,
   "payment_method": "creditcard",
   "status": 4,
   "status_display": "Payment Completed",
   "absolute_url": "https://www.ulule.com/11805/edit/order/267272/detail/",
   "resource_uri": "https://api.ulule.com/v1/orders/267272",
   "billing_address": null,
   "shipping_address": {
         "id": 555200,
         "first_name": "Florent",
         "last_name": "Foo",
         "entity_name": "",
         "address1": "8 Rue Saint-Fiacre",
         "address2": "",
         "postal_code": "75002",
         "city": "Paris",
         "state": "",
         "country": "FR",
         "user_id": 258344
   },
   "items": [
         {
            "line_total": 50,
            "line_subtotal": 50,
            "line_shipping_total": 0,
            "quantity": 1,
            "unit_price": 50,
            "reward_id": 1234,
            "reward": {
               "id": 897,
               "description_de": "",
               "description_en": "<p>an item</p>\n",
               "description_es": "",
               "description_fr": "",
               "description_it": "",
               "description_nl": "",
               "description_pt": "",
               "price": 50,
               "stock": null,
               "stock_available": null,
               "stock_taken": 236,
               "available": true,
               "shipping_nat": null,
               "shipping_int": null,
               "has_shippings": false,
               "shippings": null
            }
         }
   ],
   "created_at": "2013-09-04T19:44:10.852035Z",
   "user": {
         "id": 258344,
         "avatar": {
            "20": "https://.../site/img/avatars/avatar.20.png",
            "30": "https://.../site/img/avatars/avatar.30.png",
            "40": "https://.../site/img/avatars/avatar.40.png",
            "55": "https://.../site/img/avatars/avatar.55.png",
            "75": "https://.../site/img/avatars/avatar.75.png",
            "90": "https://.../site/img/avatars/avatar.90.png",
            "128": "https://.../site/img/avatars/avatar.128.png",
            "180": "https://.../site/img/avatars/avatar.180.png",
            "230": "https://.../site/img/avatars/avatar.230.png",
            "290": "https://.../site/img/avatars/avatar.290.png",
            "full": "https://drfhlmcehrc34.cloudfront.net/avatars/avatar.png",
            "value": "avatars/avatar.png"
         },
         "date_joined": "2013-09-04T19:43:42.636244Z",
         "first_name": "Frédéric",
         "has_avatar": false,
         "lang": "fr",
         "last_login": "2013-09-13T08:19:06.025231Z",
         "last_name": "BOULAIS",
         "birthday": "1968-05-21",
         "name": "Frédéric BOULAIS",
         "is_staff": false,
         "urls": {
            "web": {
               "absolute": "https://www.ulule.com/fredericboulais",
               "account": {
                     "wallet": "https://.../users/258344/settings/wallet"
               },
               "discussion": "https://.../discussions/compose/258344",
               "public": "https://www.ulule.com/fredericboulais"
            }
         },
         "username": "fredericboulais",
         "timezone": "Europe/Paris",
         "absolute_url": "https://www.ulule.com/fredericboulais",
         "resource_uri": "https://api.ulule.com/v1/users/258344",
         "email": "lankhou@free.fr"
   },
   "project_id": 11805
}

Cancel

POST /orders/:id/cancel

Cancel a given order.

As order cancelling is processed asynchronously, once cancelling is effective, refunded attribute will be set to true. User should be redirected to its wallet settings. The user wallet settings URL is returned in the urls attribute of user resource.

Access rights

Permissions: order owner, partner

Authentication mode:

Request

POST /orders/1/cancel HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the order identifier

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
  "order_total": "80.00",
  "order_subtotal": "80.00",
  "shipping_address": null,
  "billing_address": null,
  "items": [
      {
          "product": 1337,
          "quantity": 1,
          "line_total": "80.00",
          "unit_price": "80.00"
      }
  ],
  "id": 1,
  "refunded": true,
  "resource_uri": "https://api.ulule.com/v1/orders/1",
  "project_uri": "https://api.ulule.com/v1/projects/noob-le-film",
  "status": 6,
  "status_display": "Cancelled"
}

News

Get detail

GET /news/:id

Retrieve news detail.

Access rights

Permissions: authenticated user

Authentication mode:

Request

GET /news/1 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the news ID

Create comment

POST /news/:id/comments

Create a new comment on the news.

Access rights

Permissions: authenticated user

Authentication mode:

Request

POST /news/1/comments HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the news ID

POST parameters

comment* string the comment text

Example

{
  "comment": "a comment message"
}

List comments

GET /news/:id/comments

Retrieve news comments (organized in a pagination).

Access rights

Permissions: authenticated user

Authentication mode:

Request

GET /news/1/comments HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the news ID

Partner

Projects

List projects

GET /partners/:slug/projects

Retrieve all your projects linked to your partner website.

Access rights

Permissions: partner

Authentication mode:

Request

GET /partners/ulule/projects HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

slug the partner key identifier

Proposals

A proposal must be affected to an existing user. Before creating a proposal, your must create a new user on your partner or use an existing one and send us the access_token of the user in the workflow:

$ curl -H "Authorization: Bearer USER-ACCESS-TOKEN"  https://api.ulule.com/v1/...

There are two types of project on Ulule, when you are creating a new proposal you must specify if the proposal is for a presale or a project.

When you are submitting the type in your POST parameters you must specify the following codes:

Type Code
Presale 1
Project 2

Create “project” proposal

POST /users/:id/proposals

Create a new “project” proposal for a user.

Access rights

Permissions: partner

Authentication mode:

Request

POST /users/3048/proposals HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id the user ID

POST parameters

first_name* string the first name (30 characters max.) last_name* string the last name (30 characters max.) name* string the name of the project (255 characters max.) type* int the type of your proposal goal int the amount you have to raised currency* string the currency used, you must use a valid currency code (3 characters max.) lang* string the lang used, you must use a valid language code (5 characters max.) description* string the description rewards* string the rewards references* string the references phone_number* string (15 characters max.) nationality* string the project owner nationality, you must use a valid country code country* string you must use a valid country code (2 characters) address* string the address birthday* string the birthday of the project owner (format: YYYY-MM-DD) structure string the proposal structure (150 characters max.)

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

Create “presale” proposal

POST /users/:id/proposals

Create a new “presale” proposal for a user.

Access rights

Permissions: partner

Authentication mode:

Request

POST /users/3048/proposals HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id the user ID

POST parameters

first_name* string the first name (30 characters max.) last_name* string the last name (30 characters max.) name* string the name of the project (255 characters max.) type* int the type of your proposal nb_products_min int the number of products you have to sell currency* string the currency used, you must use a valid currency code (3 characters max.) lang* string the lang used, you must use a valid language code (5 characters max.) description* string the description rewards* string the rewards references* string the references phone_number* string (15 characters max.) nationality* string the project owner nationality, you must use a valid country code country* string you must use a valid country code (2 characters) address* string the address birthday* string the birthday of the project owner (format: YYYY-MM-DD) structure string the proposal structure (150 characters max.)

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

List proposals

GET /partners/:slug/proposals

Retrieve your proposals which had been registered in your partner website (all, new, valid and invalid).

Access rights

Permissions: partner

Authentication mode:

Request

GET /partners/ulule/proposals HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

partner the partner key identifier

Query parameters

status Optional, the current filter for proposals: new | valid | invalid

GET /partners/ulule/proposals?status=valid HTTP/1.1

Users

Create user

POST /partners/:slug/users

Create a new user for a dedicated partner.

Access rights

Permissions: partner

Authentication mode:

Request

POST /partners/ulule/users HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

partner the partner key identifier

POST parameters

username* string the username (80 characters max.) password1* string the password (128 characters max.) email* string the email (254 characters max.) lang string default user lang, if is null system lang is apply ip_address string the user IP address

(for ip_address use IPv4 format or IPv6 format)

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

{
  "date_joined": "2011-02-02T12:29:06",
  "id": 3048,
  "is_active": true,
  "last_login": "2014-02-03T10:11:26.586862",
  "public_url": "http://www.ulule.com/thoas/",
  "resource_uri": "/api/v1/users/thoas/",
  "username": "thoas"
}

List users

GET /partners/:slug/users

Retrieve all your users which had been registered in your partner website.

Access rights

Permissions: partner

Authentication mode:

Request

GET /partners/ulule/users HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

slug the partner key identifier

Partnership

Get detail

GET /partnerships/:id

Retrieve partnership detail.

Access rights

Permissions: partner

Authentication mode:

Request

GET /partnerships/1 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the partnerships ID

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
  "partner_id": 3,
  "status": "new",
  "is_default": true,
  "is_support": false
}

When you are retrieving a partnership you will except these statuses:

Label
new
pending
pending owner
pending final validation
waiting
online
validated
refused

Create partnership

POST /projects/:id/partnerships

Create partnership for a given project.

Access rights

Permissions: partner

Authentication mode:

Request

GET /projects/1/partnerships HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the project ID

POST parameters

partner_id* int The partner_id. is_default bool Tag the partnership to default. Only one partnership can be set at default is_support bool Set support tag

Example
{
  "partner_id": 2,
  "is_default": false,
  "is_support": false
}

Response

HTTP/1.1 201 CREATED
Vary: Accept
Content-Type: text/javascript

{
  "partner_id": 3,
  "status": "new",
  "is_default": true,
  "is_support": false
}

Update partnership

PATCH /partnerships/:id

Update a given partnership.

Access rights

Permissions: partner

Authentication mode:

Request

PATCH /partnerships/1 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the partnership ID

PATCH parameters

partner_id* int The partner_id. status* string The status of partnership is_default bool Tag the partnership to default. Only one partnership can be set at default is_support bool Set support tag

Example
{
  "partner_id": 2,
       "status": "new",
  "is_default": false,
  "is_support": false
}

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

{
  "partner_id": 2,
  "status": "new",
  "is_default": false,
  "is_support": false
}

Delete partnership

DELETE /partnerships/:id

Delete a given partnership.

Access rights

Permissions: partner

Authentication mode:

Request

DELETE /partnerships/1 HTTP/1.1
Host: ulule.com
Accept: application/json, text/javascript

Path parameters

id required, the partnership ID

Response

HTTP/1.1 200 OK
Vary: Accept
Content-Type: text/javascript

Workflow

Payment workflow

To create orders, we must follow this workflow:

  1. Create an access token or use Ulule Connect pop-up to retrieve this token. (endpoints are only available by access token)
  2. Update user profile to provide required information (see below)
  3. Create orders

To create orders, the order user must provide these requirements:

  • Nationality
  • Country
  • Birthday
  • First name
  • Last name
  • Email

If all these required attributes have been provided, is_completed attribute in user resource will be set to true and user will be authorized to create orders. Otherwise, is_completed attribute will be set to false and user will not be able to create any order.

To set or update these attributes, we can use the update user endpoint:

PATCH https://api.ulule.com/v1/users/:user_id

This endpoint will return the given user resource with is_completed attribute updated.

Once is_completed is true, user can create orders via the endpoint:

POST https://api.ulule.com/v1/projects/:project_id/orders

Shipping workflow

Each reward can contain shipping information, except variants.

Reward shipping attributes

Name Type Description
shipping_nat Float National shipping cost
shipping_int Float International shipping cost
shippings Array Shipping costs for specific countries
has_shippings Boolean Reward contains shippings

All these attributes can be null.

Shipping attributes

Objects contained in reward’s shippings array.

Name Type Description
countries Array Array of country codes
amount Float Shipping cost for these countries

Example

"rewards": [
    {
        "id": 160851,
        "name_de": "",
        "name_en": "",
        "name_es": "",
        "name_fr": "",
        "name_it": "",
        "name_nl": "",
        "name_pt": "",
        "description_de": "",
        "description_en": "",
        "description_es": "",
        "description_fr": "",
        "description_it": "",
        "description_nl": "",
        "description_pt": "",
        "price": 109,
        "stock": null,
        "stock_available": 100,
        "stock_taken": 347,
        "available": true,
        "shipping_nat": 10.0,
        "shipping_int": 20.0,
        "has_shippings": true,
        "shippings": [
            {
                "countries": ["FR", "ES"],
                "amount": 10.0
            },
            {
                "countries": ["RO"],
                "amount": 20.0
            }
        ]
    }
]

If we want to create an order that has rewards with shipping, we must provide the delivery country in country attribute at the order creation. Otherwise, a RequiredError error (see errors) will be returned.

For example, let’s create a presale order.

POST https://api.ulule.com/v1/projects/28994/orders

Our JSON payload:

{
    "payment_method": "creditcard",
    "return_url": "http://ulule.com",
    "rewards": [
        {"reward_id": 160851, "quantity": 1}
    ],
    "country": "FR"
}

As we are from France, we provided FR as country, this value will not be changed later. The shipping cost will be calculated for France and added to the order total amount.

A shipping address will be auto-created for this order. We will have to update this address later to fill missing attributes.

We get the created shipping address back with the shipping_address attribute:

{
    "id": 915213,
    "order_subtotal": "109.00",
    "order_total": "118.00",
    "payment_method": "creditcard",
    "status": 3,
    "status_display": "Awaiting confirmation",
    "absolute_url": "https://www.ulule.com/28994/edit/order/915213/detail/",
    "resource_uri": "https://api.ulule.com/v1/orders/915213",
    "billing_address": null,
    "shipping_address": {
        "id": 555200,
        "first_name": "",
        "last_name": "",
        "entity_name": "",
        "address1": "",
        "address2": "",
        "postal_code": "",
        "city": "",
        "state": "",
        "country": "FR",
        "user_id": 3048
    },
    "items": [
        {
            "line_total": "109.00",
            "reward_id": 160851,
            "quantity": 1,
            "unit_price": "109.00"
        }
    ],
    "created_at": "2015-06-24T14:14:33.030753Z",
    "payment_url": "https://www.ulule.com/projects/28994/checkout/pay/915213",
    "user": {
        "id": 3048,
        "avatar": {
            "20": "https://img.ulule.com/display/e3cf354766f6a7bfc4516c293e52740256a367c1/thumbnail/20x20/avatars/2015/12/07/thoas.220430.220412.png",
            "30": "https://img.ulule.com/display/ff92fbda25cf07114cdd5237d1aaa5d49818beba/thumbnail/30x30/avatars/2015/12/07/thoas.220430.220412.png",
            "40": "https://img.ulule.com/display/6b5d4bb99a33e39db9666f5464434f887c3c4a93/thumbnail/40x40/avatars/2015/12/07/thoas.220430.220412.png",
            "55": "https://img.ulule.com/display/2688744c90e156642d9121d19b934623b214f17c/thumbnail/55x55/avatars/2015/12/07/thoas.220430.220412.png",
            "75": "https://img.ulule.com/display/4925e9e0af52dfb7aeabe1f9766ae06e82deb53b/thumbnail/75x75/avatars/2015/12/07/thoas.220430.220412.png",
            "90": "https://img.ulule.com/display/60edad4deb7e9169abe7181e33a34abbc2bbf8c6/thumbnail/90x90/avatars/2015/12/07/thoas.220430.220412.png",
            "128": "https://img.ulule.com/display/a2423fb73b9a5cee6b765eb87b7bb2bb1b66b7e1/thumbnail/128x128/avatars/2015/12/07/thoas.220430.220412.png",
            "180": "https://img.ulule.com/display/aef76e959e27184091e41d36d8e73a20553386c9/thumbnail/180x180/avatars/2015/12/07/thoas.220430.220412.png",
            "230": "https://img.ulule.com/display/252d5d3d4f1deb7716a8a93fd426bfd7d9cd87d2/thumbnail/230x230/avatars/2015/12/07/thoas.220430.220412.png",
            "290": "https://img.ulule.com/display/ea3b1bae03524f06b309dc94e0b3bee3ec60f336/thumbnail/290x290/avatars/2015/12/07/thoas.220430.220412.png",
            "full": "https://drfhlmcehrc34.cloudfront.net/avatars/2015/12/07/thoas.220430.220412.png",
            "value": "avatars/2015/12/07/thoas.220430.220412.png"
        },
        "date_joined": "2011-02-02T11:29:06Z",
        "first_name": "Florent",
        "has_avatar": true,
        "lang": "fr",
        "last_login": "2016-04-01T14:26:16.705818Z",
        "last_name": "Foo",
        "birthday": "1901-04-01",
        "name": "Florent Foo",
        "is_staff": true,
        "urls": {
            "web": {
                "absolute": "https://www.ulule.com/thoas",
                "account": {
                    "wallet": "https://www.ulule.com/users/3048/settings/wallet"
                },
                "discussion": "https://www.ulule.com/discussions/compose/3048",
                "public": "https://www.ulule.com/thoas"
            }
        },
        "username": "thoas",
        "timezone": "Europe/Paris",
        "absolute_url": "https://www.ulule.com/thoas",
        "resource_uri": "https://api.ulule.com/v1/users/3048",
        "email": "florent@ulule.com"
    },
    "project_id": 28994
}

We redirect the user to the payment_url as our previous examples, nothing changed.

The user will pay on the payment form (on kolkt.com) and will return eventually on your website.

If the user returns on your website, you can ask him to update the existing shipping address:

PATCH https://api.ulule.com/v1/addresses/555200

Our JSON payload:

{
    "first_name": "Florent",
    "last_name": "Foo",
    "address1": "8 Rue Saint-Fiacre",
    "postal_code": "75002",
    "city": "Paris"
}

The country cannot be updated because the shipping amount is already calculated and the user has already paid for this amount.

We get the following response:

{
    "id": 555200,
    "first_name": "Florent",
    "last_name": "Foo",
    "entity_name": "",
    "address1": "8 Rue Saint-Fiacre",
    "address2": "",
    "postal_code": "75002",
    "city": "Paris",
    "state": "",
    "country": "FR",
    "user_id": 3048
}

Now, let’s check our previous order:

GET https://api.ulule.com/v1/orders/915213

The shipping address has been updated:

{
    "id": 915213,
    "order_subtotal": "109.00",
    "order_total": "118.00",
    "payment_method": "creditcard",
    "status": 3,
    "status_display": "Awaiting confirmation",
    "absolute_url": "https://www.ulule.com/28994/edit/order/915213/detail/",
    "resource_uri": "https://api.ulule.com/v1/orders/915213",
    "billing_address": null,
    "shipping_address": {
        "id": 555200,
        "first_name": "Florent",
        "last_name": "Foo",
        "entity_name": "",
        "address1": "8 Rue Saint-Fiacre",
        "address2": "",
        "city": "Paris",
        "postal_code": "94140",
        "country": "FR",
        "state": "",
        "user_id": 3048
    },
    "items": [
        {
            "line_total": "109.00",
            "reward_id": 160851,
            "quantity": 1,
            "unit_price": "109.00"
        }
    ],
    "created_at": "2015-06-24T14:14:33.030753Z",
    "user": {
        "id": 3048,
        "avatar": {
            "20": "https://img.ulule.com/display/e3cf354766f6a7bfc4516c293e52740256a367c1/thumbnail/20x20/avatars/2015/12/07/thoas.220430.220412.png",
            "30": "https://img.ulule.com/display/ff92fbda25cf07114cdd5237d1aaa5d49818beba/thumbnail/30x30/avatars/2015/12/07/thoas.220430.220412.png",
            "40": "https://img.ulule.com/display/6b5d4bb99a33e39db9666f5464434f887c3c4a93/thumbnail/40x40/avatars/2015/12/07/thoas.220430.220412.png",
            "55": "https://img.ulule.com/display/2688744c90e156642d9121d19b934623b214f17c/thumbnail/55x55/avatars/2015/12/07/thoas.220430.220412.png",
            "75": "https://img.ulule.com/display/4925e9e0af52dfb7aeabe1f9766ae06e82deb53b/thumbnail/75x75/avatars/2015/12/07/thoas.220430.220412.png",
            "90": "https://img.ulule.com/display/60edad4deb7e9169abe7181e33a34abbc2bbf8c6/thumbnail/90x90/avatars/2015/12/07/thoas.220430.220412.png",
            "128": "https://img.ulule.com/display/a2423fb73b9a5cee6b765eb87b7bb2bb1b66b7e1/thumbnail/128x128/avatars/2015/12/07/thoas.220430.220412.png",
            "180": "https://img.ulule.com/display/aef76e959e27184091e41d36d8e73a20553386c9/thumbnail/180x180/avatars/2015/12/07/thoas.220430.220412.png",
            "230": "https://img.ulule.com/display/252d5d3d4f1deb7716a8a93fd426bfd7d9cd87d2/thumbnail/230x230/avatars/2015/12/07/thoas.220430.220412.png",
            "290": "https://img.ulule.com/display/ea3b1bae03524f06b309dc94e0b3bee3ec60f336/thumbnail/290x290/avatars/2015/12/07/thoas.220430.220412.png",
            "full": "https://drfhlmcehrc34.cloudfront.net/avatars/2015/12/07/thoas.220430.220412.png",
            "value": "avatars/2015/12/07/thoas.220430.220412.png"
        },
        "date_joined": "2011-02-02T11:29:06Z",
        "first_name": "Florent",
        "has_avatar": true,
        "lang": "fr",
        "last_login": "2016-04-01T14:26:16.705818Z",
        "last_name": "Foo",
        "birthday": "1901-04-01",
        "name": "Florent Foo",
        "is_staff": true,
        "urls": {
            "web": {
                "absolute": "https://www.ulule.com/thoas",
                "account": {
                    "wallet": "https://www.ulule.com/users/3048/settings/wallet"
                },
                "discussion": "https://www.ulule.com/discussions/compose/3048",
                "public": "https://www.ulule.com/thoas"
            }
        },
        "username": "thoas",
        "timezone": "Europe/Paris",
        "absolute_url": "https://www.ulule.com/thoas",
        "resource_uri": "https://api.ulule.com/v1/users/3048",
        "email": "florent@ulule.com"
    },
    "project_id": 28994
}

Now, we have covered the case with reward which has shippings, even if your reward doesn’t have shippings, you can still update the order with a shipping address.

Checkout

Currently checkout pages will stay on Ulule to avoid you reimplementing payment workflow (credit card, paypal, etc.), sign up workflow, ...

Now you know how to retrieve an access_token for a user, you have to send a POST request to the project checkout page on Ulule website.

To integrate the checkout worflow of a project, this one has to be currently funding and online.

Example

We will integrate Noob, le film ! checkout page.

You will send a POST request from your website to https://fr.ulule.com/noob-le-film/checkout/ with the following parameters:

Parameter Description
reward The id of the reward you want to select
amount The amount you want to give

The reward parameter value can be found the rewards section in project api.

Control the workflow

When redirecting the user to the checkout page on Ulule, you can also control the return of the user after his contribution to your website and flag his contribution to your partner.

Parameter Description
partner The partner unique id
return_url The return url which will be inserted at the end of the process

These parameters must be added to the checkout url as query string:

https://www.ulule.com/noob-le-film/checkout/?access_token=[user-access-token]&partner=[partner-key]&return_url=http://your-site.com/

Webhooks

Use webhooks to be notified about events that happen in a Ulule account.

Webhooks solve multiple problems by letting you register a URL that we will notify you anytime an event happens in your account.

When the event occurs—for example when a successful order is made in one of your projects, Ulule creates an Event object. This object contains all the relevant information, including the type of event and the URI of the resource associated with that event.

Ulule then sends an HTTP POST request with the Event object to the registered URL in your partner account.

Configuring webhook

Configuring your server to receive a new webhook is no different from creating any page on your website.

With PHP, you might create a new .php file on your server; with a framework like Flask, you would add a new route with the desired URL.

Remember, with webhooks, your server is the server receiving the request.

Webhook data is sent as JSON in the request’s body. The full event details are included and can be used directly.

To retrieve the resource attached to the Event you may need to request the API with the uri attribute:

{
    "type": "project.created",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "project",
        "uri": "https://api.ulule.com/v1/projects/noob-le-film"
    }
}

Event list

project.created

Occurs when a project is created.

{
    "type": "project.created",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "project",
        "uri": "https://api.ulule.com/v1/projects/noob-le-film"
    }
}

project.published

Occurs when a project is put online by the project owner or the moderation team.

{
    "type": "project.published",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "project",
        "uri": "https://api.ulule.com/v1/projects/noob-le-film"
    }
}

project.submitted

Occurs when a project is submitted to moderation by a project owner.

{
    "type": "project.submitted",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "project",
        "uri": "https://api.ulule.com/v1/projects/noob-le-film"
    }
}

project.updated

Occurs whenever a project property has changed.

{
    "type": "project.updated",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "project",
        "uri": "https://api.ulule.com/v1/projects/noob-le-film"
    }
}

order.completed

Occurs when an order is completed and validated by a contributor, the following order will be reported to the project gauge.

{
    "type": "order.completed",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "order",
        "uri": "https://api.ulule.com/v1/orders/225885"
    }
}

order.cancelled

Occurs when an order is cancelled by a contribution, project properties will be updated.

{
    "type": "order.cancelled",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "order",
        "uri": "https://api.ulule.com/v1/orders/225885"
    }
}

order.reimbursed

Occurs when an order is reimbursed, project properties will be updated.

User should be redirected to its wallet settings. The user wallet settings URL is returned in the urls attribute of user resource.

{
    "type": "order.reimbursed",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "order",
        "uri": "https://api.ulule.com/v1/orders/225885"
    }
}

order.paid

Occurs when an order is set as paid, the project is successfully funded and the billing team launch payments, each order will be marked as paid.

{
    "type": "order.paid",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "order",
        "uri": "https://api.ulule.com/v1/orders/225885"
    }
}

order.updated

Occurs when an order property has changed.

{
    "type": "order.updated",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "order",
        "uri": "https://api.ulule.com/v1/orders/225885"
    }
}

proposal.validated

Occurs when a proposal is validated by the moderation team, it will create a project with the proposal properties.

{
    "type": "proposal.validated",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "proposal",
        "uri": "https://api.ulule.com/v1/proposals/6047"
    }
}

proposal.created

Occurs when a proposal is created by a user.

{
    "type": "proposal.created",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "proposal",
        "uri": "https://api.ulule.com/v1/proposals/6047"
    }
}

user.created

Occurs when a user is created.

{
    "type": "user.created",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "user",
        "uri": "https://api.ulule.com/v1/users/thoas"
    }
}

user.updated

Occurs when a user property has changed.

{
    "type": "user.updated",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "user",
        "uri": "https://api.ulule.com/v1/users/thoas"
    }
}

avatar.updated

Occurs when a user avatar has changed.

{
    "type": "avatar.updated",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "user",
        "uri": "https://api.ulule.com/v1/users/thoas"
    }
}

news.updated

Occurs when a project news property has changed.

{
    "type": "news.updated",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "news",
        "uri": "https://api.ulule.com/v1/news/72653"
    }
}

news.published

Occurs when a project news is published.

{
    "type": "news.published",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "news",
        "uri": "https://api.ulule.com/v1/news/72653"
    }
}

news.created

Occurs when a project news is created.

{
    "type": "news.created",
    "object": "event",
    "created": "2015-09-01T16:40:05.805422",
    "resource": {
        "type": "news",
        "uri": "https://api.ulule.com/v1/news/72653"
    }
}

Examples

Generate stats for a project

Generate supporters by years, months and days for a project.

We want a sortable result which will returns the number of supporters for a project organized by years, months and days, for example

{
    2014: {
        1: {
            30: 9, # 30 january 2014 - 9 supporters
            31: 10 # 31 january 2014 - 10 supporters
        },
        2: {
            1: 3 # 1 february 2014 - 2 supporters
        }
    }
}

Let’s get to work!

To send requests to api.ulule.com we will use a couple of dependencies, the first part is to install them in Python:

$ pip install requests
$ pip install python-dateutil

Now we are done, we can start our tiny script to crawl the API:

import requests

from dateutil import parser
from collections import defaultdict

ULULE_USERNAME = 'YOUR USERNAME HERE'
ULULE_PASSWORD = 'YOUR PASSWORD HERE'

def stats(slug, username, password):
    base_url = u'https://api.ulule.com/v1/%s/supporters' % slug

    results = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))

    path = '?offset=0&limit=10'

    while path is not None:
        url = base_url + path

        r = requests.get(url, auth=(username, password))

        if r.status_code != 200:
            raise Exception(u'URL %s has returned an invalid http code(%s): %s' % (
                url,
                r.status_code,
                r.text
            ))

        result = r.json()

        path = result['meta']['next']

        for supporter in supporters:
            d = parser.parse(supporter['date_creation'])

            results[d.year][d.month][d.day] += 1

    return results

results = stats('noob-le-film', ULULE_USERNAME, ULULE_PASSWORD)

Et voilà! A quick way to generate statistics for a project, be careful of the rate of your requests to not fall in rate limit.

Export project contributors in JSON format

We want to export the contributors list in plain JSON using the API.

To use this script, you will have to have special permissions on the project or be the project owner.

As the previous example, we must install third party dependencies:

$ pip install requests

Now we are done, we can start our tiny script:

# export.py
import argparse
import requests
import json

BASE_URL = 'https://api.ulule.com/v1/projects/%s/orders'

parser = argparse.ArgumentParser(description='Export contributions in .json')
parser.add_argument('--username', dest='username',
                    help='Username of the Ulule account')
parser.add_argument('--slug', dest='slug',
                    help='Slug of the project on Ulule')
parser.add_argument('--api_key', dest='api_key',
                    help='Api key of the Ulule account')
parser.add_argument('--file', dest='file',
                    help='File to export contributions in json')


def get_basic_auth_header(user, password):
    """
    Return a dict containg the correct headers to set to make HTTP Basic Auth request
    """
    user_pass = '{0}:{1}'.format(user, password)
    auth_headers = {
        'AUTHORIZATION': 'ApiKey ' + user_pass,
    }

    return auth_headers


def main(ns):
    base_url = BASE_URL % ns.slug

    url = base_url

    headers = get_basic_auth_header(ns.username, ns.api_key)

    items = {}

    while True:
        response = requests.get(url, headers=headers)

        if response.status_code != 200:
            break

        results = response.json()

        print 'Fetching %s... %s items' % (url, len(results['orders']))

        for item in results['orders']:
            if item['status'] != 7:
                continue

            items[item['id']] = item

        next_page = results['meta']['next']

        if next_page is None:
            break

        url = base_url + next_page

    return items.values()


if __name__ == '__main__':
    args = parser.parse_args()

    results = main(args)

    if args.file:
        print 'Exporting %d items to %s' % (len(results), args.file)

        with open(args.file, 'w') as file:
            file.write(json.dumps(results))

Let’s use this script:

$ python export.py --username={username-on-ulule} --api_key={my-api-key} --slug={project-identifier} --file=contributors.json

Changelog

Find below all main changes on Ulule API.

  • 2017/02/23: new attributes in user resource for endpoint list sponsorships: description, presentation and screenname.
  • 2017/02/08:
    • new attributes in user resource for endpoint get detail: description, presentation and screenname.
    • new endpoint to retrieve the user links.
    • two changes on sponsor resource, is_full attribute has been deleted and user resource has been added as an embedded resource.
  • 2017/02/06: new filters query parameters:
    • filter –> status: /users/:id/proposals?filter=new become /users/:id/proposals?status=new
    • filter –> state: /users/:id/projects?filter=created become /users/:id/projects?state=created
    • filter –> status: /partners/:slug/proposals?filter=valid become /partners/:slug/proposals?status=valid
  • 2017/01/24: the pagination system has been changed for cursor-based pagination, Offset system is deprecated but still works. More details here.

Inspirations

To build this documentation, we have taken some parts and ideas from great documentations.

We would like to thank their authors: