Hail Uploads API
The uploads API from Hail — 2 operation(s) for uploads.
The uploads API from Hail — 2 operation(s) for uploads.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/hail-so:hail-so-uploads-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hail Uploads API
description: 'Give your AI agent a voice, a real phone number, and an inbox.
This file is the source of truth for the Go CLI. Regenerate it after
changing API routes — see docs/public/contributing.md.'
version: 0.1.0
servers:
- url: https://api.hail.so
description: Hail Cloud
tags:
- name: Uploads
paths:
/v1/uploads:
post:
tags:
- Uploads
summary: Create File Upload
description: 'Make a one-time link to upload one file with a plain HTTP PUT.
For callers whose shell has no Hail key (an agent using the MCP). Send
the raw bytes to `upload_url` with `PUT` and no Authorization header,
once, before `expires_at` (15 minutes):
```
curl -sS -X PUT --data-binary @passport.jpg ''''
```
The PUT answers with `size_bytes` and `sha256` of what was stored. An
organization may hold at most 50 unused, unexpired links.'
operationId: create_file_upload
parameters:
- name: authorization
in: header
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Authorization
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FileUploadCreate'
responses:
'201':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/FileUploadLinkResponse'
'429':
description: 'Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds). Or: the organization already holds 50 unused, unexpired upload links. Use or let some expire.'
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
RateLimit-Limit:
description: The request ceiling for the current window.
schema:
type: integer
RateLimit-Remaining:
description: Requests remaining in the current window.
schema:
type: integer
RateLimit-Reset:
description: Seconds until the current window resets.
schema:
type: integer
'503':
description: File uploads are not configured on this server.
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/v1/uploads/{upload_id}:
put:
tags:
- Uploads
summary: Put File Upload
description: 'Send the file''s bytes to an upload link. No Authorization header.
Works once. A link already expired when the PUT starts is 410. The body
must arrive before the link expires (and within 10 minutes), or 408.
Storing it may end after the expiry, up to 15 minutes later. Unknown id,
wrong token, a used link, and a file that could not be stored at the end
are all 404 with one message: make a new link if the file is not
stored. A second upload of the same file is harmless.
Empty body: 422. A verification document whose first bytes are not the
declared JPEG, PNG or PDF: 422.'
operationId: put_file_upload
parameters:
- name: upload_id
in: path
required: true
schema:
type: string
format: uuid
title: Upload Id
- name: token
in: query
required: true
schema:
type: string
description: The token from `upload_url`. The only credential.
title: Token
description: The token from `upload_url`. The only credential.
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/FileUploadResponse'
'429':
description: Rate limited. This caller exceeded the general request-rate ceiling. Retry after the Retry-After header (seconds).
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
RateLimit-Limit:
description: The request ceiling for the current window.
schema:
type: integer
RateLimit-Remaining:
description: Requests remaining in the current window.
schema:
type: integer
RateLimit-Reset:
description: Seconds until the current window resets.
schema:
type: integer
'404':
description: 'Unknown link, wrong token, or already used. Also when the file could not be stored at the end: another PUT used the link first, the link was removed, or storing ended more than 15 minutes after the expiry. If your file is not stored, make a new link and send it again.'
'408':
description: The body did not arrive before the link expired, or within 10 minutes. The link is not used up.
'410':
description: The link had expired when the PUT started. Make a new link.
'413':
description: The file is larger than the link's `max_bytes`.
'503':
description: File uploads are not configured on this server, storage failed, or too many uploads are running, in all or for this organization (`Retry-After`). The link is not used up.
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
security: []
requestBody:
required: true
description: 'The raw file bytes. The `Content-Type` header is ignored: the type given when the link was made is stored.'
content:
application/octet-stream:
schema:
type: string
format: binary
components:
schemas:
FileUploadLinkResponse:
properties:
id:
type: string
format: uuid
title: Id
description: The upload's id.
purpose:
type: string
enum:
- email_attachment
- verification_document
title: Purpose
description: What the file is for.
upload_url:
type: string
title: Upload Url
description: 'Send the raw file bytes here with `PUT`. No Authorization header: the `token` in the URL is the only credential. Works once, until `expires_at`. Treat it like a password.'
method:
type: string
const: PUT
title: Method
description: The HTTP method for `upload_url`.
content_type:
type: string
title: Content Type
description: The type the file is stored with.
max_bytes:
type: integer
title: Max Bytes
description: The largest file the link takes, in bytes.
expires_at:
type: string
format: date-time
title: Expires At
description: When the link stops working.
type: object
required:
- id
- purpose
- upload_url
- method
- content_type
- max_bytes
- expires_at
title: FileUploadLinkResponse
description: 'Returned by ``POST /uploads``: a one-time link to send the bytes to.'
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
description: Path to the invalid field within the request, as a list of keys/indices (e.g. ['body', 'to']).
msg:
type: string
title: Message
description: Human-readable description of the validation failure.
type:
type: string
title: Error Type
description: Machine-readable error type code (e.g. 'missing', 'string_type').
input:
title: Input
description: The value that was actually provided and failed validation.
ctx:
type: object
title: Context
description: Additional machine-readable context for the error, when the error type provides one.
type: object
required:
- loc
- msg
- type
title: ValidationError
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
description: 'List of validation errors: each entry gives the field location (loc), the problem (msg), and the error type (type).'
type: object
title: HTTPValidationError
FileUploadResponse:
properties:
id:
type: string
format: uuid
title: Id
description: The upload's id.
purpose:
type: string
enum:
- email_attachment
- verification_document
title: Purpose
description: What the file is for.
filename:
type: string
title: Filename
description: The name given when the link was made.
content_type:
type: string
title: Content Type
description: The type given when the link was made.
size_bytes:
type: integer
title: Size Bytes
description: Bytes stored. Compare with the local file.
sha256:
type: string
title: Sha256
description: sha256 of the bytes stored, hex. Compare with the local file.
type: object
required:
- id
- purpose
- filename
- content_type
- size_bytes
- sha256
title: FileUploadResponse
description: 'Returned by ``PUT /uploads/{id}``: what Hail stored.'
FileUploadCreate:
properties:
purpose:
type: string
enum:
- email_attachment
- verification_document
title: Purpose
description: 'What the file is for. `email_attachment`: the id works in `POST /v1/emails` `attachment_ids`. `verification_document`: the id works as the `upload.<slot>` part of `POST /v1/verifications` and `POST /v1/verifications/check`.'
filename:
type: string
maxLength: 255
minLength: 1
title: Filename
description: The file's name, 1 to 255 chars, no control characters.
content_type:
type: string
title: Content Type
description: MIME type, `type/subtype` with no parameters. A `verification_document` takes `image/jpeg`, `image/png` or `application/pdf` only. This type is stored; the PUT request's own `Content-Type` header is not used.
type: object
required:
- purpose
- filename
- content_type
title: FileUploadCreate
description: 'Body of ``POST /uploads``: what the file is for, and its name and type.'