# TARTLE Developer Documentation - Complete Reference > Complete technical documentation for TARTLE's data marketplace platform APIs and integration patterns. This file contains the full content of TARTLE's developer documentation, including detailed implementation guides, API references, and code examples for DataVault Connect, Buyer API, and Endlinks. --- ## / datavault-connect / create-data-packets Source: https://documentation.tartle.co/datavault-connect/create-data-packets # Create data packets The access tokens generated by your client will be allowed to push data **only to the data packets that are associated with it**. To create a data packet and associate it with your client, click on the "+ Create Packet" button at the bottom of your client's information card in the OAuth Client page in [Developer Settings](https://source.tartle.co/buyers/developer_settings?current_tab=oauthApps). In the modal that appears, you'll need to provide the following information: - **Packet Name**: The name of the data packet. Keep it short but descriptive. - **Packet Description** (optional): Describe what the data packet contains. ```{{ title: 'Example of good values for the form fields' }} Packet Name: Acme Receipts Packet Description: A data packet of receipts synced from your Acme Receipts account. ``` Once you've filled out the form, click on 'Save' to create your data packet. Access tokens generated by your client will now allow you to push data to this packet and any other packets you make in the same manner. Think of these data packets as different products your users can sell based on the data they have in your system. For example, if your company keeps users' geolocation data and buying preferences, you can create a packet for each of them. Your users will then be able to sell each packet independently to a buyer of their choice. --- ## / datavault-connect / create-oauth-client Source: https://documentation.tartle.co/datavault-connect/create-oauth-client # Create an OAuth 2.0 client Navigate to your TARTLE [Developer Settings page](https://source.tartle.co/buyers/developer_settings?current_tab=oauthApps) on the 'OAuth App' tab. You can then click on 'New App' to begin the process of creating a new DataVault OAuth 2.0 client. We currently only support one OAuth 2.0 client per buyer account. There is, however, no limit to the number of packets you can associate to this client, enabling you to create any number of data products with the tokens they provide. In the client creation modal form, you'll need to provide the following information: - **Application Name**: This is what your users will see in the OAuth 2.0 consent screen, make it recognizable. - **Application Description**: A short description of what your application does, this will be displayed in a list of installed apps for users that use your client and how some of them will evaluate its usefulness if they are thinking about revoking access, so make it count. - **Redirect URI**: The URI to redirect to after the user has authenticated. ```{{ title: 'Example of good values for the form fields' }} Application Name: Acme Receipts Sync Application Description: Sync your receipts from Acme to TARTLE, we will create a data packet of your receipts so you can receive bids from our trusted partners. You can then sell this data to the partners you choose. Unleash the value of your data. Redirect URI: https://acme-receipts.com/auth/callback ``` Once you've filled out the form, click on 'Save' to create your client. You will be shown a modal with your `client_id` and `client_secret`. Read the instructions carefully. You will need to save your `client_secret` in a secure place as it will not be displayed again. If you lose it, you will need to delete your current application and create a new one. All currently authorized users will need to re-authorize your new application. Please don't save your `client_secret` to a GitHub or any other respository. Best practice is to save it in a secure environment like AWS Secrets Manager for use in your application and a password manager for reference. --- ## / datavault-connect Source: https://documentation.tartle.co/datavault-connect # DataVault Connect DataVault Connect is TARTLE's OAuth 2.0 implementation that enables your application to securely push data into a user's TARTLE account with their explicit consent. This integration allows your users to monetize their data from your platform through TARTLE's marketplace while maintaining full control and transparency. ## How It Works 1. **Authentication**: Your application redirects users to TARTLE's authorization page 2. **Consent**: Users authorize your application to push specific data to their TARTLE account 3. **Token Exchange**: Your application receives authorization codes that are exchanged for access tokens 4. **Data Push**: Your application uses these tokens to securely push data to the user's TARTLE account 5. **Monetization**: Users can then choose to sell this data through TARTLE's marketplace ## Implementation Overview DataVault implementation on your site is very simple and requires minimal changes to your codebase. At its core, you need to initiate a standard OAuth 2.0 Authorization flow and token exchange, save those tokens to your database, and then use them to make an API call to create a packet. The following documentation will guide you through each step of this process in detail. --- ## / datavault-connect / pushing-data-packets Source: https://documentation.tartle.co/datavault-connect/pushing-data-packets # Pushing data to TARTLE Once you have a valid access token you can use it in the `Authorization` header to push data into the TARTLE DataVault of the user that authorized your application. You will only be able to push data to the data packets you created from your OAuth application in your [developer settings](https://source.tartle.co/buyers/developer_settings/?current_tab=oauthApps). Let's imagine you want to create a data packet with user detail information like email and height/weight. After creating a packet for your OAuth Client as specified [here](/datavault-connect/create-data-packets), you can then make a `POST` request to our `/api/v5/packets//sellers_packets/push` endpoint. The following example assumes your packet id is `aJmB48qXT7NBj`. Please note that the packet id is specified both in the url segment and in the request body. These must match. ```bash {{ title: 'cURL' }} curl -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer '$ACCESS_TOKEN'" \ -d '{ "id": "aJmB48qXT7NBj", "payload": { "name": "John Doe", "email": "john.doe@example.com", "height": "180", "weight": "70" }' \ https://source.tartle.co/api/v5/packets/aJmB48qXT7NBj/sellers_packets/push }' ``` ```javascript const response = await fetch( 'https://source.tartle.co/api/v5/packets/aJmB48qXT7NBj/sellers_packets/push', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ id: 'aJmB48qXT7NBj', payload: { name: 'John Doe', email: 'john.doe@example.com', height: '180', weight: '70', }, }), }, ) const data = await response.json() ``` ```java {{ title: 'Java' }} JSONObject payload = new JSONObject() .put("name", "John Doe") .put("email", "john.doe@example.com") .put("height", "180") .put("weight", "70"); JSONObject requestBody = new JSONObject() .put("id", "aJmB48qXT7NBj") .put("payload", payload); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://source.tartle.co/api/v5/packets/aJmB48qXT7NBj/sellers_packets/push")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + accessToken) .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) .build(); HttpResponse response = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(10)) .build() .send(request, HttpResponse.BodyHandlers.ofString()); JSONObject data = new JSONObject(response.body()); ``` ```ruby {{ title: 'Ruby on rails' }} # Ruby on Rails response = HTTParty.post( 'https://source.tartle.co/api/v5/packets/aJmB48qXT7NBj/sellers_packets/push', headers: { 'Content-Type': 'application/json', 'Authorization': "Bearer #{access_token}" }, body: { id: 'aJmB48qXT7NBj', payload: { name: 'John Doe', email: 'john.doe@example.com', height: '180', weight: '70' } }.to_json, timeout: 10 ) data = JSON.parse(response.body) ``` ```python {{ title: 'Python' }} # Python response = requests.post( 'https://source.tartle.co/api/v5/packets/aJmB48qXT7NBj/sellers_packets/push', headers={ 'Content-Type': 'application/json', 'Authorization': f'Bearer {access_token}' }, json={ 'id': 'aJmB48qXT7NBj', 'payload': { 'name': 'John Doe', 'email': 'john.doe@example.com', 'height': '180', 'weight': '70' } }, timeout=10 ) data = response.json() ``` ```php {{ title: 'PHP' }} $data = [ 'id' => 'aJmB48qXT7NBj', 'payload' => [ 'name' => 'John Doe', 'email' => 'john.doe@example.com', 'height' => '180', 'weight' => '70' ] ]; $ch = curl_init('https://source.tartle.co/api/v5/packets/aJmB48qXT7NBj/sellers_packets/push'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $accessToken ]); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); ``` The id of the packet you want to push data to. The packet data you want to push. Your payload data must be an object with all values of type string. Values can also be `undefined` or `null` to save the question without an answer. The response will be in this shape: ```json { "message": "Data successfully synced", "sellers_packet": { "account_id": "", "status": "published", "updated_at": "2025-03-27T20:13:01.553Z", "id": "", "packet_id": "", "created_at": "2025-03-24T17:49:50.483Z", "beneficiary_account_id": null, "autosell_setting": "enabled_for_any_price", "first_published_at": "2025-03-24T17:49:50.483Z" } } ``` If there's an error authenticating your request, the error will be in the following format: ```json { "error": "", "error_description": "" } ``` These type of errors follow the OAuth 2.0 specification for authentication errors. And if there's any other type of error, like with data validation, the error will be in the following format: ```json { "errors": { "base": [""] } } ``` ## Token lifetime Access tokens have a lifetime of 24 hours. Once your token has expired, you will need to [refresh it](/datavault-connect/token-refresh). --- ## / datavault-connect / the-authorization-flow Source: https://documentation.tartle.co/datavault-connect/the-authorization-flow # The TARTLE Authorization Flow Once you have created a client and associated data packets with it, you're ready to implement the flow to request access to a TARTLE user's DataVault and push data onto those packets. You will need the following: - A page on your site or app where you will use a TARTLE DataVault Connect button (a styled link) to start the flow, using your [authorization url](#constructing-the-authorization-url). - A callback endpoint that will receive the redirect from the flow, verify and exchange the code for an access and refresh token pair. ## Add the button to your site The DataVault Connect button is a styled link (``) that you can use to start the authorization flow. the url attribute of the link will point to the TARTLE authorization page. The button will look like this:
DataVault Connect
There are several ways to add this button to your site, follow one of them to continue. ## Handle the redirect After the user authorizes your application from our DataVault Connect page, they will be redirected to the `redirect_uri` set in your client's settings. You must implement an endpoint for this callback url. This endpoint will be called in the following form: ``` https://your-callback-url?code=&state= ``` You will use this authorization code to call our token endpoint to exchange for an access and refresh token pair. Used to prevent CSRF attacks. Will be the value you used when constructing the authorization url. See our [Security Guide](/resources/datavault-security-guide) for more information. ## Exchange tokens Once you have received the authorization code, you can exchange it for an access and refresh token pair by calling our `/oauth/token` endpoint. ```bash {{ title: 'cURL' }} curl -X POST \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "code": "'$OAUTH_CODE'", "code_verifier": "'$CODE_VERIFIER'", "client_secret": "'$CLIENT_SECRET'", "client_id": "'$CLIENT_ID'", "redirect_uri": "'$REDIRECT_URI'", "state": "'$STATE'" }' \ https://source.tartle.co/oauth/token ``` ```javascript const response = await fetch('https://source.tartle.co/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ grant_type: 'authorization_code', code: authorizationCode, code_verifier: codeVerifier, client_secret: process.env.CLIENT_SECRET, client_id: process.env.CLIENT_ID, redirect_uri: redirectUri, state: state, }), }) const data = await response.json() ``` ```java JSONObject requestBody = new JSONObject() .put("grant_type", "authorization_code") .put("code", authorizationCode) .put("code_verifier", codeVerifier) .put("client_secret", System.getenv("CLIENT_SECRET")) .put("client_id", System.getenv("CLIENT_ID")) .put("redirect_uri", redirectUri) .put("state", state); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://source.tartle.co/oauth/token")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); JSONObject data = new JSONObject(response.body()); ``` ```ruby {{ title: 'Ruby on rails' }} # Ruby on Rails (using HTTParty) response = HTTParty.post( 'https://source.tartle.co/oauth/token', headers: { 'Content-Type': 'application/json' }, body: { grant_type: 'authorization_code', code: authorization_code, code_verifier: code_verifier, client_secret: ENV['CLIENT_SECRET'], client_id: ENV['CLIENT_ID'], redirect_uri: redirect_uri, state: state }.to_json ) data = JSON.parse(response.body) ``` ```python {{ title: 'Python' }} import requests response = requests.post( 'https://source.tartle.co/oauth/token', json={ 'grant_type': 'authorization_code', 'code': authorization_code, 'code_verifier': code_verifier, 'client_secret': os.environ.get('CLIENT_SECRET'), 'client_id': os.environ.get('CLIENT_ID'), 'redirect_uri': redirect_uri, 'state': state }, headers={'Content-Type': 'application/json'} ) data = response.json() ``` ```php {{ title: 'PHP' }} $data = [ 'grant_type' => 'authorization_code', 'code' => $authorizationCode, 'code_verifier' => $codeVerifier, 'client_secret' => getenv('CLIENT_SECRET'), 'client_id' => getenv('CLIENT_ID'), 'redirect_uri' => $redirectUri, 'state' => $state ]; $ch = curl_init('https://source.tartle.co/oauth/token'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); ``` This is a standard OAuth 2.0 parameter, set it to `authorization_code` when exchanging the authorization code for an access and refresh token pair. The authorization code you received from the redirect. The code verifier you used when constructing the authorization url. See our [Security Guide](/resources/datavault-security-guide) for more information. The client secret you saved somewhere when you created the client. This was only available to you at the time of creation and should be stored securely. See our [Security Guide](/resources/datavault-security-guide) for more information. The client id you received when you created the client and you can find it in your [developer settings](https://source.tartle.co/buyers/developer_settings/?current_tab=oauthApps). The redirect uri you used when you created the client and you can find it in your [developer settings](https://source.tartle.co/buyers/developer_settings/?current_tab=oauthApps). The state you used when you constructed the authorization url. See our [Security Guide](/resources/datavault-security-guide) for more information. You will receive a JSON response with the following properties ```json {{ title: 'Response' }} { "access_token": "", "token_type": "Bearer", "expires_in": 86400, "refresh_token": "", "scope": "push_packet", "created_at": "" } ``` Use in the `Authorization` header to push data to the data packets associated with your client. It is valid for 24 hours. After that period, you can use the `refresh_token` returned in the response to exchange for a new access token. When your access token expires, you can call the `/oauth/token` endpoint to exchange it for a new access token. After the exchange, this token will be revoked, so make sure you save the new refresh token returned in the exchange response. To exchange an access token for a refresh token, you would use the same `/oauth/token` endpoint you used to exchange the authorization code but with the following body: ```json {{ title: 'Request body' }} { "grant_type": "refresh_token", "refresh_token": "", "client_secret": "", "client_id": "", "redirect_uri": "" } ``` The response will be in the same format as before. You need to make sure you save the new refresh token returned in the exchange response, as the old one will be revoked. Congratulations! You have successfully implemented the authorization flow and are ready to begin [pushing data packets](/datavault-connect/pushing-data-packets) to your users' DataVaults. --- ## / datavault-connect / token-refresh Source: https://documentation.tartle.co/datavault-connect/token-refresh # Token expiration As a security best practice, our access tokens have a lifetime of 24 hours. After that time has elapsed, if you try to use the token to push a data packet, you will receive a `401 Unauthorized` error. You can verify the token expiration before making your request by checking the `expires_at` value in the decoded JWT (JSON Web Token). We recommend decoding the token when you receive it and saving the `expires_at` value alongside your access and your refresh token. ## Refreshing the token In order to refresh an access token you need to make a request to the `/oauth/token` endpoint as you did before but using the `refresh_token` grant type and the refresh token you saved originally. ```bash {{ title: 'cURL' }} curl -X POST \ -H "Content-Type: application/json" \ -d '{ "grant_type": "refresh_token", "refresh_token": "'$REFRESH_TOKEN'", "client_secret": "'$CLIENT_SECRET'", "client_id": "'$CLIENT_ID'", "redirect_uri": "'$REDIRECT_URI'" }' \ https://source.tartle.co/oauth/token ``` ```javascript const response = await fetch('https://source.tartle.co/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken, client_secret: process.env.CLIENT_SECRET, client_id: process.env.CLIENT_ID, redirect_uri: redirectUri, }), }) const data = await response.json() ``` ```java JSONObject requestBody = new JSONObject() .put("grant_type", "refresh_token") .put("refresh_token", refreshToken) .put("client_secret", System.getenv("CLIENT_SECRET")) .put("client_id", System.getenv("CLIENT_ID")) .put("redirect_uri", redirectUri); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://source.tartle.co/oauth/token")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString())) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); JSONObject data = new JSONObject(response.body()); ``` ```ruby {{ title: 'Ruby on rails' }} # Ruby on Rails (using HTTParty) response = HTTParty.post( 'https://source.tartle.co/oauth/token', headers: { 'Content-Type': 'application/json' }, body: { grant_type: 'refresh_token', refresh_token: refresh_token, client_secret: ENV['CLIENT_SECRET'], client_id: ENV['CLIENT_ID'], redirect_uri: redirect_uri }.to_json ) data = JSON.parse(response.body) ``` ```python {{ title: 'Python' }} import requests response = requests.post( 'https://source.tartle.co/oauth/token', json={ 'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'client_secret': os.environ.get('CLIENT_SECRET'), 'client_id': os.environ.get('CLIENT_ID'), 'redirect_uri': redirect_uri }, headers={'Content-Type': 'application/json'} ) data = response.json() ``` ```php {{ title: 'PHP' }} $data = [ 'grant_type' => 'refresh_token', 'refresh_token' => $refreshToken, 'client_secret' => getenv('CLIENT_SECRET'), 'client_id' => getenv('CLIENT_ID'), 'redirect_uri' => $redirectUri ]; $ch = curl_init('https://source.tartle.co/oauth/token'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true); ``` This is a standard OAuth 2.0 parameter, set it to `refresh_token` when refreshing the access token. The refresh token you saved originally. The client secret you saved somewhere when you created the client. This was only available to you at the time of creation and should be stored securely. See our [Security Guide](/resources/datavault-security-guide) for more information. The client id you received when you created the client and you can find it in your [developer settings](https://source.tartle.co/buyers/developer_settings/?current_tab=oauthApps). The redirect uri you used when you created the client and you can find it in your [developer settings](https://source.tartle.co/buyers/developer_settings/?current_tab=oauthApps). You can find an example of refreshing a token in the [TARTLE OAuth Test App](https://github.com/Tartle-co/tartle-oauth-testapp/blob/main/src/actions/tartleActions.ts) --- ## / endlinks / api-calls Source: https://documentation.tartle.co/endlinks/api-calls # Using Endlinks with api calls After your user has completed the flow you've implemented on your site, you need to report the outcome to TARTLE. The most flexible method for handling the result of your user's actions at the end of an endlink flow is to call one of the TARTLE Endlink endpoints from your backend or frontend code. Depending on how you [captured and saved the token](/endlinks/token-handling), you will need to then retrieve it and add it to the [appropriate endpoint](/endlinks#endlink-status-endpoints). For the examples below, we will use the `complete` endpoint, but you can use the other endpoints the same manner. As before, for a more reliable implementation, we recommend doing this server-side. ## Server-side examples ```js {{ title: 'Express' }} const reportEndlinkComplete = async (req, res) => { const token = req.session.tartle_endlink_token if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return false } try { const response = await fetch( `https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=${token}`, ) if (!response.ok) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false } return true } catch (error) { // ERROR: Log error details you might need to work with TARTLE to resolve. return false } } ``` ```js {{ title: 'Nextjs api' }} const reportEndlinkComplete = async (req) => { const session = await getSession({ req }) const token = session?.tartle_endlink_token if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return false } try { const response = await fetch( `https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=${token}`, ) if (!response.ok) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false } return true } catch (error) { // ERROR: Log error details you might need to work with TARTLE to resolve. return false } } ``` ```ruby {{ title: 'Rails' }} def report_endlink_complete token = session[:tartle_endlink_token] unless token.present? # CRITICAL: Log missing token error - you will need it to debug possible issues. return false end begin response = HTTP.get("https://source.tartle.co/api/v3/endlinks/complete", params: { tartle_endlink_token: token }) unless response.status.success? # WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false end return true rescue => e # ERROR: Log error details you might need to work with TARTLE to resolve. return false end end ``` ```java {{ title: 'Java Spring' }} public boolean reportEndlinkComplete(HttpSession session) { String token = (String) session.getAttribute("tartle_endlink_token"); if (token == null || token.isEmpty()) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return false; } try { String url = "https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=" + token; RestTemplate restTemplate = new RestTemplate(); ResponseEntity response = restTemplate.getForEntity(url, String.class); if (!response.getStatusCode().is2xxSuccessful()) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false; } return true; } catch (Exception e) { // ERROR: Log error details you might need to work with TARTLE to resolve. return false; } } ``` ```php {{ title: 'PHP' }} function reportEndlinkComplete() { $token = $_SESSION['tartle_endlink_token'] ?? null; if (!$token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return false; } try { $url = "https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=" . urlencode($token); $context = stream_context_create([ 'http' => [ 'method' => 'GET', 'header' => 'Content-Type: application/json' ] ]); $response = file_get_contents($url, false, $context); if ($response === false) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false; } $status = $http_response_header[0]; if (strpos($status, '200') === false) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false; } return true; } catch (Exception $e) { // ERROR: Log error details you might need to work with TARTLE to resolve. return false; } } ``` ```python {{ title: 'Python' }} def report_endlink_complete(request): token = request.session.get('tartle_endlink_token') if not token: # CRITICAL: Log missing token error - you will need it to debug possible issues. return False try: response = requests.get( f"https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token={token}" ) if not response.ok: # WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return False return True except Exception as e: # ERROR: Log error details you might need to work with TARTLE to resolve. return False ``` ## Client-side examples You can do this client side, but be aware that due to browser versions, security settings, and other factors, this is less reliable. Make sure you log errors thoroughly so you can debug any possible issues. ```js {{ title: 'Using localStorage' }} function reportEndlinkComplete() { const token = localStorage.getItem('tartleEndlinkToken') if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return false } try { const response = await fetch( `https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=${token}` ) if (!response.ok) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false } return true } catch (error) { // ERROR: Log error details you might need to work with TARTLE to resolve. return false } } ``` ```js {{ title: 'Using cookies' }} function reportEndlinkComplete() { const cookies = document.cookie.split(';') const tokenCookie = cookies.find(cookie => cookie.trim().startsWith('tartleEndlinkToken=')) const token = tokenCookie ? tokenCookie.split('=')[1].trim() : null if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return false } try { const response = await fetch( `https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=${token}` ) if (!response.ok) { // WARNING: Log HTTP status code and response text you might need to work with TARTLE to resolve. return false } return true } catch (error) { // ERROR: Log error details you might need to work with TARTLE to resolve. return false } } ``` ## Considerations - The `tartle_endlink_token` is only valid for one hour, so make sure you call the endpoint within that time frame. - Please make sure to log errors and failures so you can debug any issues, if you don't report an outcome due to an error, we will treat it as a failure, and the user **will not receive payments** for this packet. Having issues? see our [Endlink troubleshooting guide](/endlinks/troubleshooting). --- ## / endlinks / creating-endlinks Source: https://documentation.tartle.co/endlinks/creating-endlinks # Creating Endlinks To use Endlinks you first need to create a packet with an endlink question and submit it to TARTLE for review. Here's a quick guide for the process. ## Create a packet 1. Log in to your [buyer account](https://source.tartle.co/buyers) 2. Go to the [packet suggestion page](https://source.tartle.co/buyers/suggestions/new) 3. Give your packet a short name and a good description. 4. Click 'ADD' in the question input box 5. Enter the title and description for this question, be sure to include details on what they will be required to do when on your site and what is the benefit of doing so. 6. Select 'Endlink' as the answer type 7. Enter the link to your site. The system will automatically add a `tartle_endlink_token` url parameter to the link. This token is unique to every user & packet, and you will use it to report the results of the endlink. See the [parameters section](#parameters) below for more details. For example, you could use a packet with an endlink to direct users to open an account with your company, the packet would be published once they have successfully done so. If you have a standing bid for 30 packets at $10 a packet, you will effectively be paying the first 30 users to sign up a $10 bonus. ![Example endlink question](/endlink-question-modal.png) This is a simple example, but the actions taken on your site could be anything you want. You just need to take care of correctly reporting the results to TARTLE following the next guide. ## Endlink parameters The system will automatically add a `tartle_endlink_token` and `seller_id` url parameter to the link without you having to do anything. You will need the token as explained on the rest of the guides in this documentation section. The `seller_id` is added for your convenience if you need it for associating your user with ours. However, in some cases you may need more information or more control over the name of the parameter. In that case you can use double curly braces with a variable name to rename the url parameter when you're creating the question. For example the example above, if your system required the token be named simply `token` then you would enter `https://teams.com?token={{endlink_token}}` as the endlink url. The supported variables are: The token that was present as a parameter to the endlink URL when the user landed on your site from TARTLE, use this when you need to change the name of the default `tartle_endlink_token` parameter. The id of the seller that clicked the link, use this when you need to change the name of the `seller_id` parameter. This property is **not** included automatically but you can add it using double curly braces. You could need this if you have several packets with us and you need to identify this particular user + packet interaction in your system You do **not** need to add anything to your url if you don't need to change the name of the default parameters, `tartle_endlink_token` and `seller_id` are added automatically by the system. ## The TARTLE packet review process Once you have submitted your packet, it will be reviewed by TARTLE. If you'd like to speed up the process, you can contact us at support@tartle.co to request an expedited review. Next you will need to integrate token handling on your site. You can do this on the server or the client. We **strongly recommend** doing this on the server side as it is more reliable than doing it on the browser due to browser versions, browser security, and other issues. Continue on to [token handling](/endlinks/token-handling) --- ## / endlinks Source: https://documentation.tartle.co/endlinks # Endlinks Introduction Endlinks are a powerful feature of the TARTLE marketplace that enable a simple integration between TARTLE and external systems. They provide a mechanism to direct sellers from TARTLE to your website or application while maintaining contextual information about the interaction. ## What are Endlinks? An Endlink is a special type of question within a TARTLE data packet that contains a URL specified by the buyer. When a seller views the packet, this URL is presented as a clickable link. The system adds specific parameters to the URL, primarily the `tartle_endlink_token`, which is a secure token which will allow TARTLE to identifiy both the seller and the specific packet question later when the buyer calls the Endlink endpoints. ## Key Characteristics - Endlinks are typically designed to be the only question in a packet - They serve as a bridge between TARTLE and external systems - They enable verification and feedback for user actions on external systems - They support various integration patterns (api calls, redirects, or with a widget) ## The typical endlink flow There are a number of ways to capture and save the token, as well as how to report it back to TARTLE. Later in this guide we will show you those options alongside our recommendations. ## Endlink Status Endpoints ```text {{ title: 'Called as api call'}} https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token= https://source.tartle.co/api/v3/endlinks/fail?tartle_endlink_token= https://source.tartle.co/api/v3/endlinks/quota_full?tartle_endlink_token= https://source.tartle.co/api/v3/endlinks/screenout?tartle_endlink_token= ``` ```text {{ title: 'Called as redirect'}} https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token= https://source.tartle.co/api/v3/endlinks/fail.html?tartle_endlink_token= https://source.tartle.co/api/v3/endlinks/quota_full.html?tartle_endlink_token= https://source.tartle.co/api/v3/endlinks/screenout.html?tartle_endlink_token= ``` The token that was present as a parameter to the endlink URL when the user landed on your site from TARTLE. - **Complete**: Marks the packet as successfully completed and publishes it. - **Fail**: Marks the interaction as failed and filters the packet from the seller's list - **Quota Full**: Indicates your quota is full and filters the packet from the seller's list - **Screen Out**: Indicates the seller did not qualify and filters the packet from the seller's list The following pages will guide you through the steps for creating a packet with an endlink, extracting and storing the token on your site and using it to report the results of the user's actions. Continue to the [Creating endlinks](/endlinks/creating-endlinks) page to get started. --- ## / endlinks / redirects Source: https://documentation.tartle.co/endlinks/redirects # Using Endlinks with browser redirects After your user has completed the flow you've implemented on your site, you need to report the outcome to TARTLE. One approach is to use browser redirects to send users directly to the appropriate TARTLE endpoint. This method is very simple to implement, but will take users away from your site. Depending on how you [captured and saved the token](/endlinks/token-handling), you will need to retrieve it and include it in the redirect URL to the [appropriate endpoint](/endlinks#endlink-status-endpoints). For the examples below, we will use the `complete` endpoint, but you can use the other endpoints in the same manner. ## Server-side examples These examples show how to implement redirects from your server: ```js {{ title: 'Express' }} const redirectToTartleComplete = (req, res) => { const token = req.session.tartle_endlink_token if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. return res.status(400).send('Cannot complete TARTLE flow: Missing token') } // Redirect the user to the TARTLE complete endpoint return res.redirect( `https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=${token}`, ) } ``` ```js {{ title: 'Nextjs api' }} const redirectToTartleComplete = async (req, res) => { const session = await getSession({ req }) const token = session?.tartle_endlink_token if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. console.error('No TARTLE token found in session') return res .status(400) .json({ error: 'Cannot complete TARTLE flow: Missing token' }) } // Redirect the user to the TARTLE complete endpoint return res.redirect( `https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=${token}`, ) } ``` ```ruby {{ title: 'Rails' }} def redirect_to_tartle_complete token = session[:tartle_endlink_token] unless token.present? # CRITICAL: Log missing token error - you will need it to debug possible issues. Rails.logger.error 'No TARTLE token found in session' redirect_to error_path, alert: 'Cannot complete TARTLE flow: Missing token' return end # Redirect the user to the TARTLE complete endpoint redirect_to "https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=#{token}" end ``` ```java {{ title: 'Java Spring' }} @GetMapping("/complete-tartle-flow") public String redirectToTartleComplete(HttpSession session, RedirectAttributes redirectAttrs) { String token = (String) session.getAttribute("tartle_endlink_token"); if (token == null || token.isEmpty()) { // CRITICAL: Log missing token error - you will need it to debug possible issues. logger.error("No TARTLE token found in session"); redirectAttrs.addFlashAttribute("error", "Cannot complete TARTLE flow: Missing token"); return "redirect:/error"; } // Redirect the user to the TARTLE complete endpoint return "redirect:https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=" + token; } ``` ```php {{ title: 'PHP' }} function redirectToTartleComplete() { $token = $_SESSION['tartle_endlink_token'] ?? null; if (!$token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. error_log('No TARTLE token found in session'); header('Location: /error.php?message=Missing+TARTLE+token'); exit; } // Redirect the user to the TARTLE complete endpoint header('Location: https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=' . urlencode($token)); exit; } ``` ```python {{ title: 'Python (Flask)' }} @app.route('/complete-tartle-flow') def redirect_to_tartle_complete(): token = session.get('tartle_endlink_token') if not token: # CRITICAL: Log missing token error - you will need it to debug possible issues. app.logger.error('No TARTLE token found in session') flash('Cannot complete TARTLE flow: Missing token', 'error') return redirect(url_for('error_page')) # Redirect the user to the TARTLE complete endpoint return redirect(f'https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token={token}') ``` ## Client-side examples You can also implement redirects client-side. You could add the complete url as the `href` attribute to an anchor tag or use the `window.location.href` property to redirect the user. ```html {{ title: 'As link' }} Get your reward ``` ```js {{ title: 'Using localStorage' }} function redirectToTartleComplete() { const token = localStorage.getItem('tartleEndlinkToken') if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. console.error('No TARTLE token found in localStorage') return false } // Redirect the user to the TARTLE complete endpoint window.location.href = `https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=${token}` return true } ``` ```js {{ title: 'Using cookies' }} function redirectToTartleComplete() { const cookies = document.cookie.split(';') const tokenCookie = cookies.find((cookie) => cookie.trim().startsWith('tartleEndlinkToken='), ) const token = tokenCookie ? tokenCookie.split('=')[1].trim() : null if (!token) { // CRITICAL: Log missing token error - you will need it to debug possible issues. console.error('No TARTLE token found in cookies') return false } // Redirect the user to the TARTLE complete endpoint window.location.href = `https://source.tartle.co/api/v3/endlinks/complete.html?tartle_endlink_token=${token}` return true } ``` ## Considerations - The `tartle_endlink_token` is only valid for one hour, so make sure you perform the redirect within that time frame. - Browser redirects will take users away from your site. If you want to provide a seamless experience, consider using the [API-based approach](/endlinks/api-calls) instead. - Please make sure to log errors and failures so you can debug any issues, if you don't report an outcome due to an error, we will treat it as a failure, and the user **will not receive payments** for this packet. Having issues? See our [Endlink troubleshooting guide](/endlinks/troubleshooting). --- ## / endlinks / scripts Source: https://documentation.tartle.co/endlinks/scripts # Using Endlinks with embeddable scripts We also offer another option for very simple use cases that only need to report success. TARTLE provides two javascript files you can embed on your html via ` ``` For an example of a page that would work as an endlink destination, see this [example landing page](https://github.com/Tartle-co/tartle-platform-examples/blob/main/endlinks/widgets/index.html) ## Complete script Requirements: 1. The page where you embedded the landing script **must** be different than the one where you embed the complete script. 2. There must be no redirects in this flow, if the user is redirected to log in automatically for example, the script will not have enough time to save the token. ```html {{ title: 'Embedding the complete script' }} ``` This script will make the api call as soon as the script is loaded and executed, so it's best to make a completion page to inform the user they are done and they were successful, and embedding the script on that page. Here is an example of a [completion page](https://github.com/Tartle-co/tartle-platform-examples/blob/main/endlinks/widgets/final.html) ## Best use cases When using these scripts you can only report sucesss (completion). Any user packets that go to your endlink url and don't end up in a completion page will remain incomplete. This is why it's important that you make sure you follow the requirements in both sections above to avoid false negatives where the user completes your requirements but completion is not reported, which will result in loss of possible profits for the user. The best use case for this is for sites that are rendered as separate html pages, and flows that do not rely on redirects upon landing. Even in the best of circumnstances, since this is a client-based flow you can end up with false positives because of old browsers, security settings that prevent access to localStorage, and other issues. For better reliability, we recommend using the server examples for [api calls](/endlinks/api-calls) or [browser redirects](/endlinks/redirects). --- ## / endlinks / spa Source: https://documentation.tartle.co/endlinks/spa # Using Endlinks in single page applications If you use a single page application framework like React, Angular or Vue, and you have a server-side, the recommended approach is to use the [api calls](/endlinks/api-calls) solution. However if you have a Single Page App and need to use endlinks on browser, you can't rely on our widgets since technically everything lives in the same page. Instead you can use your application state to persist the token across pages as well as use `localStorage`, cookies or your database solution if you expect the completion of your flow to happen beyond this session. For an example of how to use React Context to persist the token below. But you could as well use Redux or another state management solution. ## Using React Context For example using this React Context to wrap around your app, you can set and retrieve the token from different components: ```jsx {{ title: 'React Context example' }} const EndlinkContext = createContext(null) export function EndlinkTokenProvider({ children }) { const [endlinkToken, setEndlinkToken] = useState(null) useEffect(() => { console.log('Endlink token was set to', endlinkToken) }, [endlinkToken]) return ( {children} ) } export function useEndlinkToken() { const context = useContext(EndlinkContext) if (!context) { throw new Error( 'useEndlinkToken must be used within a EndlinkTokenProvider', ) } return context } ``` ## Setting the token On the component that loads in your landing url (the endlink url you added to the packet), you would set the token using the `setEndlinkToken` function from the hook in the context file_get_contents ```jsx {{ title: 'Setting the token' }} const { endlinkToken, setEndlinkToken } = useEndlinkToken() // Capture the token on mount useEffect(() => { const searchParams = new URLSearchParams(window.location.search) const endlinkToken = searchParams.get('tartle_endlink_token') || null if (endlinkToken) { setEndlinkToken(endlinkToken) } }, []) ``` ## Retrieving and using the token Then later in the component that loads when your user has completed the flow you would retrieve the token and make the appropriate call to one of our [Endlink endpoints](/endlinks#endlink-status-endpoints). ```jsx {{ title: 'Retrieving the token' }} const { endlinkToken } = useEndlinkToken() // ... // Later, you can make the api call depending on the outcome. 'GET' is implied but added for clarity. const endlinkUrl = `https://source.tartle.co/api/v3/endlinks/complete?tartle_endlink_token=${endlinkToken}` fetch(endlinkUrl, { method: 'GET' }) ``` For a full SPA Example using the components above, see our [example github repo](https://github.com/Tartle-co/tartle-platform-examples/tree/main/endlinks/spa) --- ## / endlinks / token-handling Source: https://documentation.tartle.co/endlinks/token-handling # Handling the endlink token Below we will show you to different ways to handle the endlink token, server-side and client-side. We **strongly recommend** doing this on the server side if it is possible for your use case, as it is more reliable than doing it on the browser due to browser versions, browser security settings, and other issues. ## Extract and save the token on the server. When a user arrives at your site via an Endlink, you need to capture the `tartle_endlink_token` from the URL, you will need to save this token securely in your system so you can later report the desired outcome for the data packet. Whenever possible, **we recommend you capture and save this token server-side**, as doing so client-side is error prone because of browser versions, security restrictions, and other issues. ```js {{ title: 'Express' }} const saveToken = (req) => { const token = req.query.tartle_endlink_token if (token) { req.session.tartle_endlink_token = token return true } return false } ``` ```js {{ title: 'Nextjs api' }} const saveToken = async (req) => { const token = req.query.tartle_endlink_token if (!token) { return false } const session = await getSession({ req }) session.tartle_endlink_token = token await session.save() return true } ``` ```ruby {{ title: 'Rails' }} def save_token token = params[:tartle_endlink_token] if token.present? session[:tartle_endlink_token] = token true else false end end ``` ```java {{ title: 'Java Spring' }} public boolean saveToken(HttpServletRequest request, HttpSession session) { String token = request.getParameter("tartle_endlink_token"); if (token != null && !token.isEmpty()) { session.setAttribute("tartle_endlink_token", token); return true; } return false; } ``` ```php {{ title: 'PHP' }} function saveToken() { $token = $_GET['tartle_endlink_token'] ?? null; if ($token) { $_SESSION['tartle_endlink_token'] = $token; return true; } return false; } ``` ```python {{ title: 'Python' }} def save_token(request, session): token = request.args.get('tartle_endlink_token') if token: session['tartle_endlink_token'] = token return True return False ``` ## Extract and save the token on the client. When doing it on the server is not possible, you can handle the token on the client with javascript. Storing it in localStorage or cookies is the most common approach. For security reasons, the token is only valid for one hour and only works with this user/packet interaction. ```js {{ title: 'Using localStorage' }} function saveTokenWithLocalStorage() { const urlParams = new URLSearchParams(window.location.search) const token = urlParams.get('tartle_endlink_token') if (token) { localStorage.setItem('tartleEndlinkToken', token) console.log('Token saved to localStorage') } else { console.error('No tartle_endlink_token found in URL parameters') } } ``` ```js {{ title: 'Using cookies' }} function saveTokenWithCookies() { const urlParams = new URLSearchParams(window.location.search) const token = urlParams.get('tartle_endlink_token') if (token) { const expirationDate = new Date() expirationDate.setTime(expirationDate.getTime() + 24 * 60 * 60 * 1000) document.cookie = `tartleEndlinkToken=${token}; expires=${expirationDate.toUTCString()}; path=/; SameSite=Strict` console.log('Token saved to cookies') } else { console.error('No tartle_endlink_token found in URL parameters') } } ``` You can now walk the user through the flow you've implemented on your site to capture whetever actions you need. This could be any flow, the only limitation is that the `tartle_endlink_token` is only valid for one hour, so they must be able to complete your flow in that amount of time. Once you know the outcome of the user's actions (is this a success, failure, etc?) you need to report it to TARTLE. You have a few options on how to do this. --- ## / endlinks / troubleshooting Source: https://documentation.tartle.co/endlinks/troubleshooting # Frequent issues and how to troubleshoot them If you are having troubly successfully completing an endlink flow, the most common mistakes are: ## Browser limitations Some older browsers do not support the `localStorage` API, so our widgets will not work in those browsers, leading to users successfully completing your flow but not publishing the corresponding data packet, losing profit potential. In other instances, the user may have disabled localStorage in their modern browser manually for security reasons. For these reasons, we recommend using the server-side examples for [api calls](/endlinks/api-calls) or [browser redirects](/endlinks/redirects) as they result in more reliable outcomes. ## Using a token twice leads to an error If you try to use the same token for more than one call to any endlink endpoint, you will get an error after the first call, since they are single use only. This can happen, for example, in React if you don't manage your state correctly and depend on useEffect to make the call. Note that even though you get an error the second time, the original call succeeded so the user's data packet was published. ## Your endlink url is not the actual final destination of a user when clicked. If you're saving the token in the client, you have to make sure that the page the user lands on (the endlink url) is the actual final destination of the user in all circumstances. Meaning that you can't redirect users to log in or to a different page, unless you **forward the url parameters** from the original landing url so you can capture it later. If you automatically redirect the user upon landing without forwarding url parameters, the client might start loading the page and it might even run some of the page's javascript before the redirect happens, so if you use the widget or client js to save the token, it won't be reliable and your completion endpoint call or widget will fail later, losing profit potential for your user. ## A note on error logging Under the best of circumstances, some errors might still result in a successful completion of your requirements without a successful call to the complete endpoint. For example there can be transient network errors when making the call to TARTLE. In use cases where your use case involves a reward to your user, it's important to log any errors when calling the endlink endpoints alongside the token in question, so you can let us know and we can manually make sure the affected user is paid. --- ## / resources / datavault-button-manual Source: https://documentation.tartle.co/resources/datavault-button-manual # Adding the DataVault Connect button manually If you can't or don't want to use [the script method](./datavault-button-script), you can manually add the DataVault Connect button to your site. by following the steps below: ## Build the authorization url You'll need to have a page on your site or app where you will use a TARTLE DataVault Connect button to start the flow. This is not an actual ` From there you should [create a wallet](https://source.tartle.co/buyers/wallet) and fund it to start bidding on user data. And now you're ready. As a developer, you can interact with the TARTLE platform in three primary ways: ## DataVault Connect DataVault Connect enables your application to become a trusted data source in the TARTLE ecosystem by implementing OAuth 2.0 authentication, allowing you to securely sync and upload data packets to TARTLE on behalf of consenting users. These users can then receive and accept bids for this data through the TARTLE marketplace. By implementing DataVault Connect, your users can securely monetize information that was previously locked in your system, while you share in the revenue and maintain your commitment to user privacy and data ownership. Possible Use Cases: - Fitness applications can allow users to monetize their workout and health data - Financial services can enable customers to sell anonymized transaction patterns - Health platforms can help patients benefit from sharing medical insights - Smart device manufacturers can create revenue streams from device usage data
## Buyer API The TARTLE Buyer API allows you to programmatically manage your buyer account, create and monitor bids, access purchased data, build cohorts, and automate your data acquisition workflows through a comprehensive REST API. Possible Use Cases: - Market research firms can automate collection of consumer sentiment data - AI companies can programmatically source training data for machine learning models - Financial analysts can gather economic behavior patterns across demographics - Product teams can collect feedback from specific user segments at scale
## Endlinks Endlinks provide a way for directing users from a TARTLE data packet to your website, using a provided token, your application can later inform TARTLE whether a user completed a required action, was screened out, or encountered other outcomes, allowing for seamless integration between external workflows and TARTLE's data marketplace. Possible Use Cases: - Survey companies can direct users to complete questionnaires and report completion status back to TARTLE - Research organizations can screen participants based on custom criteria and only publish data for qualified respondents - Service providers can validate user actions on their platform before data is published to the marketplace
## Guides ---