8 min read

Submissions API

Create, retrieve, update, and list form submissions programmatically using the Grader.io REST API.

Overview

The Submissions API allows you to manage form submissions and lead data programmatically. Use it to integrate Grader.io with your existing systems, build custom dashboards, or automate lead processing workflows.

Authentication

All API requests require authentication using your API key:

curl -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     https://api.grader.io/v1/submissions

Get your API key from Dashboard → Settings → API Keys.

Endpoints Overview

MethodEndpointDescription
POST
/v1/submissions
Create new submission
GET
/v1/submissions
List submissions with filters
GET
/v1/submissions/{id}
Get specific submission
PUT
/v1/submissions/{id}
Update submission data
DELETE
/v1/submissions/{id}
Delete submission
POST
/v1/submissions/{id}/rescore
Re-evaluate submission

Create Submission

Submit form data for scoring and processing.

Request

POST /v1/submissions
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

{
  "graderId": "grader_abc123",
  "source": "website_form",
  "formData": {
    "email": "john.doe@acme.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "Acme Corporation",
    "jobTitle": "VP Marketing",
    "phone": "+1-555-0123",
    "website": "https://acme.com",
    "message": "Interested in your enterprise solution",
    "customField1": "High Priority",
    "customField2": "Referral from partner"
  },
  "metadata": {
    "ip": "192.168.1.1",
    "userAgent": "Mozilla/5.0...",
    "referrer": "https://google.com",
    "campaign": "fall-2023-enterprise"
  }
}

Response

{
  "id": "sub_xyz789",
  "graderId": "grader_abc123",
  "source": "website_form",
  "status": "scored",
  "score": 87,
  "grade": "hot",
  "formData": {
    "email": "john.doe@acme.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "Acme Corporation",
    "jobTitle": "VP Marketing",
    "phone": "+1-555-0123",
    "website": "https://acme.com",
    "message": "Interested in your enterprise solution",
    "customField1": "High Priority", 
    "customField2": "Referral from partner"
  },
  "routing": {
    "assignedTo": "rep_sarah_johnson",
    "team": "enterprise_sales",
    "territory": "west_coast"
  },
  "createdAt": "2023-10-15T14:30:00Z",
  "updatedAt": "2023-10-15T14:30:02Z"
}

Parameters

FieldTypeRequiredDescription
graderId
stringYesGrader to process submission
source
stringNoSource identifier (default: "api")
formData
objectYesForm field data
metadata
objectNoAdditional context data

List Submissions

Retrieve submissions with filtering and pagination.

Request

GET /v1/submissions?grader=grader_abc123&score_min=80&limit=50&page=1
Authorization: Bearer YOUR_API_KEY

Query Parameters

ParameterTypeDescriptionExample
grader
stringFilter by grader ID
grader_abc123
score_min
numberMinimum score (0-100)
80
score_max
numberMaximum score (0-100)
90
grade
stringFilter by grade
hot
,
warm
,
cold
source
stringFilter by source
website_form
date_from
stringStart date (ISO 8601)
2023-10-01T00:00:00Z
date_to
stringEnd date (ISO 8601)
2023-10-31T23:59:59Z
email
stringFilter by email address
john.doe@acme.com
company
stringFilter by company name
Acme Corporation
assigned_to
stringFilter by assigned rep
rep_sarah_johnson
status
stringFilter by status
new
,
scored
,
routed
limit
numberResults per page (max 100)
50
page
numberPage number
1
sort
stringSort field
created_at
,
score
,
updated_at
order
stringSort direction
asc
,
desc

Response

{
  "data": [
    {
      "id": "sub_xyz789",
      "graderId": "grader_abc123",
      "score": 87,
      "grade": "hot",
      "formData": {
        "email": "john.doe@acme.com",
        "company": "Acme Corporation",
        "jobTitle": "VP Marketing"
      },
      "routing": {
        "assignedTo": "rep_sarah_johnson"
      },
      "createdAt": "2023-10-15T14:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 847,
    "pages": 17,
    "hasNext": true,
    "hasPrevious": false
  },
  "filters": {
    "grader": "grader_abc123",
    "score_min": 80
  }
}

Get Submission

Retrieve detailed information about a specific submission.

Request

GET /v1/submissions/sub_xyz789
Authorization: Bearer YOUR_API_KEY

Response

{
  "id": "sub_xyz789",
  "graderId": "grader_abc123",
  "source": "website_form",
  "status": "scored",
  "score": 87,
  "grade": "hot",
  "formData": {
    "email": "john.doe@acme.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "Acme Corporation",
    "jobTitle": "VP Marketing",
    "phone": "+1-555-0123",
    "website": "https://acme.com",
    "message": "Interested in your enterprise solution"
  },
  "scoreBreakdown": {
    "emailDomain": 25,
    "jobTitle": 20,
    "companySize": 15,
    "industry": 12,
    "formCompletion": 10,
    "geography": 5
  },
  "routing": {
    "assignedTo": "rep_sarah_johnson",
    "team": "enterprise_sales",
    "territory": "west_coast",
    "assignedAt": "2023-10-15T14:30:03Z"
  },
  "metadata": {
    "ip": "192.168.1.1",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
    "referrer": "https://google.com/search?q=lead+scoring",
    "campaign": "fall-2023-enterprise"
  },
  "history": [
    {
      "event": "created",
      "timestamp": "2023-10-15T14:30:00Z",
      "user": "api"
    },
    {
      "event": "scored",
      "timestamp": "2023-10-15T14:30:02Z",
      "score": 87,
      "grader": "grader_abc123"
    },
    {
      "event": "routed",
      "timestamp": "2023-10-15T14:30:03Z",
      "assignedTo": "rep_sarah_johnson"
    }
  ],
  "createdAt": "2023-10-15T14:30:00Z",
  "updatedAt": "2023-10-15T14:35:00Z"
}

Update Submission

Modify submission data and optionally trigger re-scoring.

Request

PUT /v1/submissions/sub_xyz789
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY

{
  "formData": {
    "email": "john.doe@acme.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "Acme Corporation",
    "jobTitle": "Senior VP Marketing",
    "phone": "+1-555-0123",
    "website": "https://acme.com",
    "message": "Interested in your enterprise solution for Q1 rollout",
    "customField1": "Budget Approved",
    "customField2": "Decision Maker Confirmed"
  },
  "rescore": true
}

Response

{
  "id": "sub_xyz789",
  "status": "scored",
  "score": 92,
  "grade": "hot",
  "formData": {
    "email": "john.doe@acme.com",
    "firstName": "John",
    "lastName": "Doe",
    "company": "Acme Corporation",
    "jobTitle": "Senior VP Marketing",
    "phone": "+1-555-0123",
    "website": "https://acme.com",
    "message": "Interested in your enterprise solution for Q1 rollout",
    "customField1": "Budget Approved",
    "customField2": "Decision Maker Confirmed"
  },
  "updatedAt": "2023-10-15T15:45:00Z"
}

Re-score Submission

Trigger re-evaluation of a submission with current grader criteria.

Request

POST /v1/submissions/sub_xyz789/rescore
Authorization: Bearer YOUR_API_KEY

Response

{
  "id": "sub_xyz789",
  "status": "scored",
  "previousScore": 87,
  "newScore": 92,
  "scoreChange": 5,
  "previousGrade": "hot",
  "newGrade": "hot",
  "rescoreDate": "2023-10-15T16:00:00Z"
}

Delete Submission

Remove a submission and all associated data.

Request

DELETE /v1/submissions/sub_xyz789
Authorization: Bearer YOUR_API_KEY

Response

{
  "deleted": true,
  "id": "sub_xyz789",
  "deletedAt": "2023-10-15T16:15:00Z"
}

Error Responses

400 Bad Request

{
  "error": "validation_error",
  "message": "Invalid request data",
  "details": [
    {
      "field": "formData.email",
      "error": "Invalid email format"
    },
    {
      "field": "graderId",
      "error": "Grader not found"
    }
  ]
}

401 Unauthorized

{
  "error": "unauthorized",
  "message": "Invalid API key"
}

404 Not Found

{
  "error": "not_found",
  "message": "Submission not found"
}

429 Too Many Requests

{
  "error": "rate_limit_exceeded",
  "message": "Rate limit exceeded",
  "retryAfter": 60
}

Rate Limits

API requests are rate-limited by plan:

PlanRequests/minuteBurst limit
Starter60100
Pro300500
Elite1,2002,000
EnterpriseCustomCustom

Rate limit headers are included in all responses:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 299
X-RateLimit-Reset: 1634567890

SDKs and Libraries

JavaScript/Node.js

const GraderIO = require('@graderio/sdk');

const client = new GraderIO({
  apiKey: process.env.GRADER_API_KEY
});

// Create submission
const submission = await client.submissions.create({
  graderId: 'grader_abc123',
  formData: {
    email: 'john.doe@acme.com',
    company: 'Acme Corporation'
  }
});

console.log(`Submission scored: ${submission.score}`);

Python

import graderio

client = graderio.Client(api_key=os.environ['GRADER_API_KEY'])

# List high-scoring submissions
submissions = client.submissions.list(
    score_min=80,
    limit=25
)

for submission in submissions:
    print(f"{submission.email}: {submission.score}")

PHP

use GraderIO\Client;

$client = new Client(['api_key' => $_ENV['GRADER_API_KEY']]);

// Get submission details
$submission = $client->submissions->get('sub_xyz789');

echo "Score: " . $submission->score . "\n";
echo "Grade: " . $submission->grade . "\n";

Webhooks Integration

Combine the Submissions API with webhooks for powerful workflows:

// Webhook handler that enriches data via API
app.post('/grader-webhook', async (req, res) => {
  const { event, data } = req.body;
  
  if (event === 'submission.scored' && data.submission.score >= 90) {
    // Fetch full details for high-value leads
    const fullSubmission = await client.submissions.get(data.submission.id);
    
    // Enrich with additional data
    await client.submissions.update(fullSubmission.id, {
      formData: {
        ...fullSubmission.formData,
        priority: 'urgent',
        followUpDate: new Date(Date.now() + 24*60*60*1000).toISOString()
      }
    });
    
    // Notify sales team
    await notifyHighValueLead(fullSubmission);
  }
  
  res.json({ received: true });
});

Best Practices

Batch Processing

Process multiple submissions efficiently:

// Bad: Individual API calls
for (const lead of leads) {
  await client.submissions.create(lead);
}

// Good: Batch processing
const batch = leads.slice(0, 100); // Process in chunks
const promises = batch.map(lead => client.submissions.create(lead));
const results = await Promise.all(promises);

Error Handling

Implement robust error handling:

async function createSubmissionWithRetry(data, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await client.submissions.create(data);
    } catch (error) {
      if (error.status === 429) {
        // Rate limited - wait and retry
        await new Promise(resolve => setTimeout(resolve, error.retryAfter * 1000));
        continue;
      } else if (error.status >= 500) {
        // Server error - retry with backoff
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        continue;
      } else {
        // Client error - don't retry
        throw error;
      }
    }
  }
  throw new Error('Max retries exceeded');
}

Caching

Cache frequently accessed data:

const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 300 }); // 5 minute cache

async function getSubmission(id) {
  const cacheKey = `submission_${id}`;
  let submission = cache.get(cacheKey);
  
  if (!submission) {
    submission = await client.submissions.get(id);
    cache.set(cacheKey, submission);
  }
  
  return submission;
}

Next Steps